Passkeys (WebAuthn)
Phishing-resistant authentication with passkeys in Wocha SDKs and Kratos
Passkeys are a modern, phishing-resistant authentication method built on the Web Authentication (WebAuthn) standard. Wocha uses Ory Kratos as the identity backend, which supports passkey registration and passwordless login natively. The Wocha SDKs expose this capability through React hooks, Next.js route handlers, and UI components.
What are passkeys?
A passkey is a cryptographic credential stored on a user's device (or synced via iCloud Keychain, Google Password Manager, etc.). Instead of typing a password, the user verifies their identity with a biometric or device PIN.
Benefits:
- Phishing-resistant — credentials are bound to your site's domain (relying party ID)
- No shared secrets — the private key never leaves the device
- Better UX — one tap or biometric instead of remembering passwords
- Sync across devices — platform providers sync passkeys where enabled
Enabling passkeys in Wocha
Passkeys are configured in Kratos. Ensure WebAuthn is enabled in your Kratos configuration:
Important settings:
| Setting | Description |
|---|---|
rp.id | Must match the domain users authenticate against (e.g. example.com) |
rp.origins | Must include the exact URL (scheme + host + port) of pages that trigger WebAuthn |
passwordless | When true, users can sign in with a passkey alone (no password) |
For Wocha Cloud tenants, WebAuthn is enabled by default on the hosted auth UI. For self-hosted deployments, update your Kratos config and redeploy.
Set WOCHA_KRATOS_URL in your Next.js app if Kratos is not at the same origin as WOCHA_ISSUER:
Next.js route handlers
Mount the passkey API routes in your App Router project. These handlers proxy to Kratos WebAuthn endpoints and manage CSRF cookies.
Create matching routes for all four endpoints:
| Route | Handler export |
|---|---|
POST /api/auth/passkeys/register/options | registerOptions |
POST /api/auth/passkeys/register/verify | registerVerify |
POST /api/auth/passkeys/authenticate/options | authenticateOptions |
POST /api/auth/passkeys/authenticate/verify | authenticateVerify |
Each handler proxies to the corresponding Kratos self-service endpoints:
GET /self-service/login/flows?id={flowId}— includes WebAuthn challenge optionsPOST /self-service/login— submitwebauthn_logincredentialGET /self-service/registration/flows?id={flowId}— registration challengePOST /self-service/registration— submitwebauthn_registercredential
React hooks
usePasskey
The hook calls your Next.js passkey routes at /api/auth/passkeys by default. Override with:
Low-level utilities
For custom UI, use the utility functions directly:
UI components
PasskeyButton
For registration (e.g. in account settings):
SignIn with passkeys
Enable the passkey button on the pre-built SignIn component:
When passkeys={true}, a "Sign in with passkey" button appears between the primary sign-in action and social providers.
Conditional UI (passkey autofill)
Browsers that support Conditional UI can show passkeys in the username field autofill menu. Use useConditionalUI to enable this:
Requirements for conditional UI:
- Input must have
autoComplete="username webauthn"(orwebauthnalone) - Page must be served over HTTPS (or localhost)
- Browser must support
PublicKeyCredential.isConditionalMediationAvailable()
Call abort() when navigating away to cancel any pending WebAuthn prompt.
Browser support matrix
| Browser | Passkeys | Conditional UI | Notes |
|---|---|---|---|
| Chrome 108+ | ✅ | ✅ (109+) | Sync via Google Password Manager |
| Safari 16+ | ✅ | ✅ (16+) | Sync via iCloud Keychain |
| Firefox 119+ | ✅ | ❌ | Passkeys work; autofill not yet supported |
| Edge 108+ | ✅ | ✅ | Same engine as Chrome |
| Safari iOS 16+ | ✅ | ✅ | Face ID / Touch ID |
| Chrome Android 108+ | ✅ | Partial | Platform authenticator required |
Use isWebAuthnSupported() and isConditionalMediationAvailable() to feature-detect at runtime.
Security considerations
- Passkey routes must be served from the same origin as your app so Kratos CSRF cookies are forwarded correctly.
- Set
rp.idandrp.originsin Kratos to match your production domain — mismatches cause WebAuthn failures. - Registration typically requires an existing session (settings flow) or can be part of the signup flow depending on your Kratos configuration.
- For OAuth-integrated apps, passkey login establishes a Kratos session; you may still need to complete the Hydra OAuth flow for API tokens depending on your architecture.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| "WebAuthn is not supported" | Non-HTTPS origin, or browser too old |
| "WebAuthn is not enabled" in API response | Kratos webauthn.enabled: false or flow missing webauthn nodes |
SecurityError in browser | rp.id / origins mismatch with current URL |
| CSRF errors from Kratos | Cookies not forwarded — ensure credentials: "include" on fetch |
| Passkey prompt never appears | User has no registered passkeys for this RP ID |
See also: Getting started