Passkeys
Configure passwordless passkey sign-in and account-managed WebAuthn credentials.
Passkeys let users authenticate with the same biometric, device PIN or external security key they use to unlock a trusted authenticator. The starter kit integrates Better Auth's passkey plugin with registration, sign-in and account-management UI.
Included flow
- Sign in with passkey appears below the configured OAuth providers.
- Signed-in users manage passkeys from Dashboard → Settings → Security.
- A user can register multiple passkeys, give each one a recognizable name, rename it later and remove it with confirmation.
- Registration and authentication require WebAuthn user verification.
- Browser ceremony errors are mapped to actionable messages instead of a generic authentication failure.
The feature is enabled by default:
export const authConfig = {
// ... other settings
enablePasskeys: true
};Setting enablePasskeys to false removes the sign-in and account-management
UI and stops registering the passkey server plugin. It therefore disables the
corresponding Better Auth endpoints as well.
Database migration
Passkey metadata is stored in the passkey table. The table records the public
credential, counter, authenticator information, optional display name and the
owning user. Private key material never leaves the user's authenticator.
New downloads include the ORM-specific migration. Existing projects must apply it before deploying the passkey-enabled application:
npm run db:migrateNo new environment variable is required.
Require user verification
The server registers the plugin with userVerification: 'required' and checks
the authentication result before creating a session:
passkey({
authenticatorSelection: {
userVerification: 'required'
},
authentication: {
afterVerification: async ({ verification }) => {
if (!verification.authenticationInfo.userVerified) {
throw new APIError('UNAUTHORIZED', {
code: 'PASSKEY_USER_VERIFICATION_REQUIRED',
message:
'Verify your identity with a PIN or biometric to use this passkey.'
});
}
}
}
});This protects against accepting a ceremony that proves possession of an authenticator without proving the person using it. Keep both the WebAuthn option and the server-side result check when adapting the integration.
Passkeys and TOTP
A user-verified passkey is treated as the complete passwordless sign-in method. It does not redirect to a second TOTP challenge. Password sign-in still follows the user's configured Better Auth two-factor flow.
This distinction avoids asking for two independent possession checks during a single passkey ceremony while preserving TOTP for password-based authentication. If your product requires a separate step-up challenge for a sensitive action, implement and test that policy around the action instead of assuming every authentication method passes through the password hook.
HTTPS and relying-party scope
WebAuthn requires a secure context in production. Browsers allow localhost
during development, but deployed passkeys are scoped to their relying party and
origin. Test registration and authentication on the same production domain your
customers will use.
Changing domains later can prevent existing credentials from matching the new relying party. Plan custom domains and authentication subdomains before relying on passkeys as the only recovery path.
Test the complete ceremony
The starter kits include a Playwright test backed by Chromium's virtual WebAuthn authenticator. It verifies registration, naming, rename, sign-out, passwordless sign-in and deletion. It also switches user verification off and confirms the server rejects the ceremony before retrying with verification enabled.
Run the focused test with:
npm run with-dev-env -- playwright test tests/e2e/passkeys.spec.ts --project=chromiumKeep a normal password or recovery strategy available while evaluating browser, platform-authenticator and security-key support for your customer base.