Passkey: pencere, otomatik doldurma, geçiş
Birkaç satırda passkey öncelikli MFA: korunan her işlem için hazır bir onay penceresi, e-posta alanının otomatik doldurmasında sunulan passkey'ler ve parola kullanıcılarını taşıyan, tarayıcı başına bir kez gösterilen "Passkey ekle" önerisi.
<touchque-approval> (her sayfa) ve <TouchqueApproval> (React), herhangi bir sunucu SDK'sıyla korunan bir rotayı çağırır ve yalnız onay istendiğinde açılır. Telefonda seçilecek numarayı, ilk bağlama QR'ını, çevrimdışı QR ve kodu, passkey adımını ve retleri olay kodlarıyla gösterir. --tq-approval-* CSS değişkenleriyle biçimlenir; her metin değiştirilebilir.
Tarayıcı, sitenize ait passkey'leri e-posta alanının önerilerinde listeler (WebAuthn conditional mediation); birini seçmek e-posta yazmadan girişi tamamlar. Asla açılır pencere açmaz: desteklemeyen tarayıcı hiçbir şey göstermez, normal formunuzu tutun. Relay'den e-postasız seçenek istenir; bunlar sitenizin her passkey'ini kabul eder ve seçilen passkey hesabı belirler.
touchqueRouter bunu yürütür, passkeys.upgrade() numarayı onStep ile gösterir. Telefon yoksa relay'inizin kayıt rotası cevap vermeden önce kişiyi siz kanıtlayın (az önce yazılan parola ya da son birkaç dakikadaki giriş).| # | Adım | Nasıl |
|---|---|---|
| 1 | Sitenizde passkey'i açın | Dashboard → App user policy → "Sitenizde passkey": alan adınız (RP ID) ve giriş sayfası adresleri, sonra "Parolasız giriş". Kullanıcılar için henüz bir şey değişmez. |
| 2 | Relay'i bağlayın | touchqueRouter (Node), Flask/Django/FastAPI blueprint'i, TouchqueRelay::routes (Laravel), NewRelayHandler (Go), MapTouchqueRelay (ASP.NET Core), Spring Boot starter'ın relay'i ya da Touchque::Rack::Relay (Rails, Rack); getUserId oturumdaki kullanıcıyı döner. Giriş, otomatik doldurma, kayıt ve onayları o sunar. |
| 3 | Giriş formuna otomatik doldurmayı ekleyin | E-posta alanına autocomplete="username webauthn", form görününce passkeys.autofill(). Passkey'i olan kullanıcı onu alandan seçer; diğerleri eskisi gibi girer. |
| 4 | Parolalı girişten sonra "Passkey ekle" önerin | Parolalı ya da e-posta bağlantılı girişten sonra shouldOfferPasskey(), ardından passkeys.upgrade() ya da dismissPasskeyOffer(). Tarayıcı başına bir kez; "Şimdi değil" orada hatırlanır. |
| 5 | Benimsemeyi izleyin | PASSKEY_REGISTERED / PASSKEY_DELETED webhook'ları ya da passkey.added üzerinde bir Action kimin geçtiğini söyler; GET /webauthn/credentials bir kullanıcının passkey'lerini listeler. |
| 6 | Sonra gereken yerde zorunlu kılın | Kritik işlemler (politika) ya da adminler (require_passkey_for_role Action şablonu) için passkey'i zorunlu kılın. Kullanıcıların çoğunun iki passkey'i olana kadar parolayı ve telefonu kurtarma yolu olarak tutun; kayıp telefonu Never Locked Out karşılar. |
@touchque/web 2.2.0, @touchque/react 2.1.0, sunucu SDK'ları 5.2.0: relay'in kullanıcı adsız seçenekleri 4.0.0'da geldi; bu sayfadaki adlar (requireTouchque, touchqueRouter) 5.0'ınkiler (4.x'te TouchQue yazılıyordu).
import { touchqueRouter, requireTouchque } from '@touchque/node';
// Tarayıcının çağırdığı relay: passkey girişi (otomatik doldurma dahil), kayıt, onay
app.use('/touchque', touchqueRouter(tq, {
getUserId: (req) => req.session.user?.email, // passkey kaydı için oturumdaki kullanıcı
getLoginUser: (req) => req.session.passwordOk, // parola adımını geçen kullanıcı
onAuthenticated: (req, res, { externalUsername }) => { req.session.user = { email: externalUsername }; },
}));
// Telefon onayı isteyen bir işlem: tek satır
app.post('/api/transfer', requireTouchque('SEND_MONEY'), sendMoney);from touchque.contrib.flask import touchque_relay_blueprint, require_touchque
app.register_blueprint(touchque_relay_blueprint(
tq,
get_user_id=lambda req: session.get('user'),
get_login_user=lambda req: session.get('password_ok'),
), url_prefix='/touchque')
@app.post('/api/transfer')
@require_touchque('SEND_MONEY')
def transfer(): ...// routes/web.php (Laravel)
TouchqueRelay::routes(app('router'), 'touchque');
// routes/api.php
Route::post('/transfer', [TransferController::class, 'store'])
->middleware('touchque:SEND_MONEY');mux.Handle("/touchque/", http.StripPrefix("/touchque", touchque.NewRelayHandler(tq, touchque.RelayOptions{
UserID: func(r *http.Request) string { return currentUser(r) },
})))
mux.Handle("/api/transfer", touchque.Require(tq, "SEND_MONEY", http.HandlerFunc(transferHandler), touchque.RequireOptions{
User: func(r *http.Request) string { return currentUser(r) }, // zorunlu
}))app.MapTouchqueRelay("/touchque", new RelayOptions<HttpContext>
{
GetUserId = http => http.User.TouchqueUser(),
});
app.MapPost("/api/transfer", SendMoney).RequireAuthorization().RequireTouchque("SEND_MONEY");@Bean // the relay, served at /touchque
RelayOptions<HttpServletRequest> touchqueRelay() {
return new RelayOptions<HttpServletRequest>(TouchqueSpring::user);
}
@PostMapping("/api/transfer")
@RequireTouchque("SEND_MONEY")
Receipt sendMoney(@RequestBody Transfer t) { … }# config/routes.rb
mount Touchque::Rack::Relay.new(get_user_id: ->(req) { req.session["user"] }) => "/touchque"
# app/controllers/transfers_controller.rb
include Touchque::Rails::Guard
require_touchque "SEND_MONEY", only: :create<!-- Herhangi bir sayfa, framework gerekmez -->
<touchque-approval base-url="/touchque"></touchque-approval>
<script type="module">
import { defineTouchqueApproval } from '@touchque/web';
defineTouchqueApproval();
const dialog = document.querySelector('touchque-approval');
const res = await dialog.fetch('/api/transfer', { method: 'POST', body });
</script>import { useTouchqueApproval, TouchqueApproval } from '@touchque/react';
const approval = useTouchqueApproval({ baseUrl: '/touchque' });
<button onClick={() => approval.fetch('/api/transfer', { method: 'POST', body })}>Send</button>
<TouchqueApproval approval={approval} /><input type="email" name="email" autocomplete="username webauthn">
<script type="module">
import { createTouchqueWeb } from '@touchque/web';
const tq = createTouchqueWeb({ baseUrl: '/touchque' });
const result = await tq.passkeys.autofill({ input: 'input[name=email]' });
if (result?.ok) location.href = '/home'; // null: no autofill here, keep the form
</script>const autofill = usePasskeyAutofill({ baseUrl: '/touchque' }, {
onResult: (r) => r.ok && navigate('/home'),
});
<input type="email" name="email" {...autofill.inputProps} />import { createTouchqueWeb, shouldOfferPasskey, dismissPasskeyOffer } from '@touchque/web';
const tq = createTouchqueWeb({ baseUrl: '/touchque', credentials: 'include' });
// Right after a password or email-link sign-in, before you leave the page:
if (await shouldOfferPasskey({ user: email })) {
showOffer({
add: () => tq.passkeys.upgrade({ user: email, onStep: showPhoneNumber }), // a linked phone approves first
notNow: () => dismissPasskeyOffer(email), // remembered in this browser
});
}const offer = usePasskeyUpgrade({ baseUrl: '/touchque', credentials: 'include' }, user.email);
{offer.status === 'offer' && (
<Banner onAdd={() => offer.upgrade()} onLater={offer.dismiss}
number={offer.step?.number} />
)}