Add passkeys
Enable passkey sign-in and give users a screen to manage their credentials.
Passkeys let users sign in with an authenticator instead of typing a password. Discoverable passkeys can also identify the account without an email address. When the authenticator verifies the person with a fingerprint, face, or device PIN, the session reaches two-factor assurance.
Enable passkeys per product. They can replace a password as the first factor or satisfy a second-factor challenge after another sign-in method.
Before you start
- Complete the Users quickstart and mount
UsersProvider. - Choose your allowed origins before enrollment. Each credential is bound to a relying-party domain that stays fixed for its lifetime.
- Have a browser with WebAuthn and a platform or security-key authenticator for testing.
What your users get
Discoverable sign-in: on supported browsers, the user can select a passkey from autofill over the email field without entering an identifier.
Domain-bound credentials: the browser restricts a credential to its relying-party domain, preventing a look-alike site from requesting it for a different domain. Lepsto stores the public key, not the private signing key.
Two-factor assurance: an assertion with user verification produces aal2. See the assurance table.
Named credentials: users can enroll passkeys on their devices and distinguish them by name, transport, and last-used time.
Turn them on
- Open Users → Configuration → Passkeys and enable Offer passkeys. The product API accepts the same setting in
passkeys.enabled:
{ "passkeys": { "enabled": true } }- Set the relying-party domain. If all allowed origins use one host, leave Relying party ID empty to derive it from that host. For multiple hosts, specify their shared parent as a bare domain without a scheme or port. For example,
app.example.comandadmin.example.comcan use:
{ "passkeys": { "enabled": true, "rpId": "example.com", "rpName": "Acme" } }Replace the example domain and product name with yours. Configure authentication explains all fields and the validation rules for deriving or accepting the domain.
Sign in with one
- Render
<SignIn/>from@lepsto/sdk/users/react/uiwith the callback, challenge, and state from the quickstart:
import { SignIn } from '@lepsto/sdk/users/react/ui'
export function PasskeySignIn({ codeChallenge, state }) {
return (
<SignIn
redirectUri="https://app.example.com/auth/callback"
codeChallenge={codeChallenge}
state={state}
/>
)
}Replace the callback with your exact allowlisted URI. Before rendering, your backend generates the PKCE pair and state, retains the verifier, and passes only the challenge and state to this component. On completion, the component form-POSTs the code and state to the callback. Your backend validates state and exchanges the code before creating the application session.
The component offers a passkey button when the browser supports WebAuthn and conditional autofill when available. Set passkeys={false} to hide both offers when the product has not enabled passkeys.
For a custom form, usePasskeySignIn exposes feature detection, the ceremony, and its result. It does not establish your application session: provide the callback and backend-generated challenge, then handle the returned code. See Client libraries for the hook and the passkey handoff for completion.
Verify it worked
- Sign in and add a passkey from your settings page. Check its name in the list.
- Sign out and look for the passkey button. A browser with conditional mediation can also offer autofill over the email field.
- Sign in with the passkey. On your backend, inspect the verified token: an assertion with user verification has
aal: 'aal2'andwebauthninamr.
If the button is missing, check browser support and the component’s passkeys setting. If sign-in returns passkey_not_enabled, enable the product’s relying-party configuration. Autofill requires conditional mediation in addition to basic WebAuthn support.
What a passkey sign-in is worth
What the authenticator did | The session |
|---|---|
Verified the user — fingerprint, face, PIN | aal2, and the second-factor challenge is skipped |
Presence only, no verification | aal1, and the second-factor challenge runs as usual |
An assertion with user verification proves both possession and user verification in one ceremony. The flow completes at aal2, records webauthn in amr, and satisfies mfa.required without another challenge.
Presence without user verification counts as one factor. The flow offers the account’s other factors, excluding passkeys so the same method cannot serve as both factors.
Use a passkey as the second factor
After a password, email code, magic link, or OAuth sign-in, an enrolled passkey can answer the second-factor challenge. <SignIn/> offers it when the attempt’s factors includes webauthn. In a custom form, call submitSecondFactorPasskey().
See Two-factor authentication for the rest of the challenge screen.
Let users manage them
- Add
<PasskeySettings/>underUsersProviderto give users a list with add, rename, and remove actions:
import { SignedIn } from '@lepsto/sdk/users/react'
import { PasskeySettings } from '@lepsto/sdk/users/react/ui'
export function SecuritySettings() {
return (
<SignedIn>
<PasskeySettings />
</SignedIn>
)
}<UserProfile/> already includes this component in Security, beside two-step verification. A separate settings component is optional when that panel is present.
Adding, renaming, and removing require fresh proof of identity. The component handles the prompt and accepts a passkey as well as a code, including for accounts whose only credential is a passkey.
Handle a lost device
A user signed in on another device can remove the lost device’s passkey in settings. Revocation takes effect immediately.
If the user cannot sign in, an operator can select Revoke on the passkey row in the user record’s MFA factors section. Revocation is also available through the product API and to an agent. It requires a written reason, records that reason verbatim in the audit trail, and takes effect immediately. It still cannot remove the account’s last sign-in method. See User management.
Lepsto support cannot remove a user’s passkey, and operators cannot enroll one for the user.
Browser support
Use feature detection: supported controls the WebAuthn button and autofillAvailable controls conditional autofill. Browsers without those capabilities can still show the product’s other methods. No user-agent detection is needed.
Next steps
- Configure authentication for the relying-party domain and passkey policy.
- Client libraries for hooks, components, and headless calls.
- Authentication flows for the ceremony inside an attempt.
- Two-factor authentication for challenge and recovery behavior.
- Sessions and tokens for
aalandamr.