Skip to content

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

  1. Open Users → Configuration → Passkeys and enable Offer passkeys. The product API accepts the same setting in passkeys.enabled:
{ "passkeys": { "enabled": true } }
  1. 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.com and admin.example.com can 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

  1. Render <SignIn/> from @lepsto/sdk/users/react/ui with 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

  1. Sign in and add a passkey from your settings page. Check its name in the list.
  2. Sign out and look for the passkey button. A browser with conditional mediation can also offer autofill over the email field.
  3. Sign in with the passkey. On your backend, inspect the verified token: an assertion with user verification has aal: 'aal2' and webauthn in amr.

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

  1. Add <PasskeySettings/> under UsersProvider to 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

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select