- MFA step-up factor — the user already has an authenticated session and proves a passkey to acquire a sensitive scope. Driven by
continueWithPasskey. - Primary-factor sign-in — the user signs in with just a passkey, no email/phone OTP, no password. Driven by
loginWithPasskey. RequiresPasskeyConfig.login_enabledserver-side.
Detect WebAuthn support
Gate the UI on theisPasskeySupported() helper. Returns false on browsers without a usable WebAuthn implementation (older Safari, headless contexts, some embedded webviews).
Register a passkey
Registration runs inside an authenticated session and consumes theprld:passkey:write scope, typically obtained via a step-up challenge just before enrolment so adding an authenticator always requires an additional ownership proof.
| Field | Required | Description |
|---|---|---|
username | Yes | WebAuthn user.name shown by the authenticator. Typically the user’s primary email or phone. |
displayName | No | WebAuthn user.displayName shown alongside the name. Falls back to username. |
nickname | No | Server-side-only label for the “Manage your passkeys” UI (“MacBook”, “YubiKey 5C”). |
excludeCredentials so the same authenticator can’t be registered twice — duplicate registration is the idempotent alreadyRegistered: true path.
The SDK invalidates its cached session and refreshes after a successful enrolment, so the next access token reflects the consumed scope and the (optionally mapped) has_passkey claim.
Sign in with a passkey (primary factor)
loginWithPasskey opens a WebAuthn ceremony with empty allowCredentials so the browser surfaces every discoverable credential it holds for the configured RPID. The server resolves the user from the assertion’s userHandle — no identifier is sent from the client.
Requires PasskeyConfig.login_enabled server-side. While the flag is on, registration also requests residentKey: required so the credential is discoverable.
Conditional UI (autofill)
Passmediation: "conditional" to surface matching credentials in the username-field autofill chip instead of a modal authenticator picker. Pair with an AbortSignal so the SDK call cancels cleanly when the user picks a different sign-in method.
Complete a verify_passkey step-up step
WhenrequestStepUp returns a challenge whose first step is verify_passkey, complete it with continueWithPasskey. The SDK caches the assertion options under the challenge id, so the call is parameter-light:
continueWithPasskey runs navigator.credentials.get() against the cached options, posts the assertion to /stepup/continue, and the SDK refreshes the session automatically.
Manage registered passkeys
A “Manage your passkeys” UI uses three endpoints under/me/passkeys.
List
Rename
prld:passkey:write, the same scope as registration — drive a step-up first if the session doesn’t hold it. Pass an empty string to clear the label.
Delete
Deletion requiresprld:passkey:write — removing an authenticator is a sensitive operation, so it needs the same fresh step-up as registration rather than relying on the ambient session.
has_passkey custom claim.
Try it
Try it
Replace
src/App.jsx with:src/App.jsx
Error catalogue
| Class | Triggered by | Typical recovery |
|---|---|---|
PrldErrors.PasskeyNotSupported | Browser has no WebAuthn implementation | Fall back to a different sign-in method |
PrldErrors.PasskeyNotConfigured | App has no PasskeyConfig, or login_enabled: false on the login endpoints | Configure the Relying Party / flip the flag |
PrldErrors.PasskeyRegistrationFailed | The attestation failed verification | Retry, or route to a different authenticator |
PrldErrors.PasskeyStepUnavailable | No credentials, signature mismatch, or sign-count regression on a step-up assertion | Fall back to an OTP step |
PrldErrors.Unauthorized | loginWithPasskey — unknown user or bad assertion (single 401 by design) | Prompt the user to try again or pick a different method |
PrldErrors.InsufficientScope | registerPasskey / renamePasskey / deletePasskey without prld:passkey:write | Drive a step-up to grant the scope, then retry |
PrldErrors.RateLimited | Too many begin ceremonies | Honor Retry-After and back off |
PrldErrors.NotFound | renamePasskey / deletePasskey with an unknown credential id | Reload the list |
What’s next?
- Step-Up Authentication — the SDK surface for
requestStepUpand howcontinueWithPasskeyplugs into it. - Passkey reference — full ceremony walk-through, security model, AAGUID policy, webhook events.
- Passkey integration guide — backend curl flow to configure the Relying Party identity, step-up, and (optional) passwordless / enterprise policy.