Skip to main content

Passkeys (WebAuthn)

Passkeys are phishing-resistant, hardware-backed credentials that ride on the W3C WebAuthn API. IdentSphere supports them as both a sign-in factor and an MFA-style second factor.

Enrollment

Two-step ceremony:

  1. POST /v1/users/me/passkeys/register/begin returns PublicKeyCredentialCreationOptions.
  2. Browser calls navigator.credentials.create() with those options.
  3. POST /v1/users/me/passkeys/register/complete with the result.

@identsphere/react's usePasskeyEnroll hook wraps the three steps.

Sign-in

POST /v1/auth/passkey/login/begin { email }
→ PublicKeyCredentialRequestOptions

browser: navigator.credentials.get(...)

POST /v1/auth/passkey/login/complete { credential }
→ full LoginResponse::Success (cookies set)

The resulting session is AAL2; mfa_verified_at is populated.

Configuration

The Relying Party identity must match your deployment:

FieldExample
rp_idauth.example.com (host only, no scheme/port)
rp_originhttps://auth.example.com (full URL including scheme)
rp_nameAcme (shown to user by some authenticators)

In development, localhost is the only host browsers accept without HTTPS. Production requires cookies_secure = true and the SDK to be served from the origin matching rp_origin exactly.

Storage

WhatWhere
Credential IDuser_passkeys.credential_id (base64url)
Public key + sign counter (serialized webauthn-rs Passkey)user_passkeys.public_key (base64url(JSON))
Per-user passkey count cap10
In-flight challenge statepasskey_challenges (5-min TTL)

Limits

  • v0.1 requires an email at login/begin. Discoverable (resident-key) login lands later.
  • Max 10 enrolled passkeys per user.
  • Conditional UI (autofill from the browser) requires the webauthn-rs conditional-ui feature flag — not enabled in v0.1.

Best practices

  1. Encourage passkey enrollment in onboarding. They're the strongest factor available to consumer hardware.
  2. Keep passwords as a fallback for password-managed accounts. Users who lose every passkey AND their authenticator need to be able to recover via password + email.
  3. Prefer passkey + password over passkey-only until passkey portability across cloud providers stabilizes.

What MFA-vs-Passkey looks like in code

import { useSession } from '@identsphere/react';

function MfaLevel() {
const { data } = useSession();
if (!data) return null;
const method = data.user.auth_method;
// 'password' | 'password_with_mfa' | 'passkey' | 'api_key' | 'social_oauth'
const aalLabel = ({
'password': 'AAL1',
'password_with_mfa': 'AAL2',
'passkey': 'AAL2',
'social_oauth': 'AAL1',
'api_key': 'AAL1',
} as const)[method];
return <span>{aalLabel}</span>;
}