Passkeys
Sign users in with a passkey (WebAuthn / FIDO2) — Face ID, Touch ID, Windows Hello or a security key — with no password: registration, sign-in, managing passkeys and how it is protected.
A passkey is a key pair kept by the user's device or password manager. Signing in with one proves possession of the device and the user's fingerprint, face or PIN, so it counts as two factors: the session is aal2, like password + MFA.
How it works
| Step | Route | Browser call |
|---|---|---|
| Start registration (signed in) | POST /api/v1/auth/passkeys/register/start | navigator.credentials.create({ publicKey: options }) |
| Finish registration | POST /api/v1/auth/passkeys/register/finish | — |
| Start sign-in | POST /api/v1/auth/passkeys/authenticate/start | navigator.credentials.get({ publicKey: options }) |
| Finish sign-in | POST /api/v1/auth/passkeys/authenticate/finish | — returns a session |
| List / remove | GET /api/v1/auth/passkeys, DELETE /api/v1/auth/passkeys/{id} | — |
import { startAuthentication } from '@simplewebauthn/browser' // handles the base64url encoding
const base = 'https://<project-ref>.sovarias.in/api/v1/auth/passkeys'
const headers = { 'Content-Type': 'application/json', apikey: ANON_KEY }
const options = await fetch(`${base}/authenticate/start`, { method: 'POST', headers, body: '{}' }).then(r => r.json())
const assertion = await startAuthentication({ optionsJSON: options.publicKey ?? options })
const session = await fetch(`${base}/authenticate/finish`, {
method: 'POST', headers, body: JSON.stringify(assertion),
}).then(r => r.json()) // { access_token, refresh_token, … }Protection
- Each challenge is valid for 5 minutes and can be used once.
- The authenticator's signature counter is checked; a counter that goes backwards (a cloned authenticator) is refused.
- Passkeys are bound to your site's domain by the browser, so a phishing site can't use them.
Set passkeys up under Authentication › Passkeys (passkeys work on your own domain).
Something unclear or missing on this page?Tell us