Passkeys and two factor authentication are configured through Better Auth and exposed through the account settings UI. Users can add a passkey, enable two factor authentication, download backup codes, change their password state, and disable two factor authentication when needed.
Client Plugins
The browser auth client enables passkeys, two factor authentication, admin helpers, and last used login method tracking.
export const authClient = createAuthClient({
baseURL: ClientEnv.NEXT_PUBLIC_DOMAIN,
plugins: [
adminClient(),
passkeyClient(),
twoFactorClient({
twoFactorPage: WebRoutes.signIn.path,
}),
lastLoginMethodClient(),
],
})The server config mirrors those capabilities in src/features/auth/lib/auth.ts.
plugins: [
passkey({
rpName: SiteConfig.name,
rpID: ServerEnv.BETTER_AUTH_PASSKEY_RP_ID,
origin: ServerEnv.NEXT_PUBLIC_DOMAIN,
}),
twoFactor({
issuer: SiteConfig.name,
}),
lastLoginMethod(),
]Passkeys
Passkeys are managed from account settings in src/features/settings/components/settings-account-section/settings-account-section.tsx.
When the user adds a passkey, the app checks whether the current device supports a platform authenticator. It then asks Better Auth to create the passkey.
await authClient.passkey.addPasskey({
authenticatorAttachment: hasPlatformAuthenticator ? "platform" : "cross-platform",
})For local development, BETTER_AUTH_PASSKEY_RP_ID should be localhost. For production, set it to the real application domain without protocol.
BETTER_AUTH_PASSKEY_RP_ID="localhost"Passkey records are stored through Better Auth. See the Database section for database structure details.
Two Factor Authentication
Two factor authentication uses TOTP. Users enable it from account settings by confirming their password, scanning the generated QR code, and verifying the code from their authenticator app.
const response = await authClient.twoFactor.enable({
password: ui.twoFactorPassword.trim(),
})
await authClient.twoFactor.verifyTotp({
code: ui.twoFactorVerificationCode.trim(),
})If a user signed up without a password, the settings UI asks them to create a password before enabling two factor authentication. This keeps the disable and recovery flow predictable.
Backup Codes
After TOTP verification succeeds, Better Auth returns backup codes. The UI shows them once and lets the user download them.
const content = `${backupCodes.join("\n")}\n`
const blob = new Blob([content], { type: "text/plain;charset=utf-8" })Backup codes should be treated as sensitive recovery credentials. The app does not show them again after the setup dialog is closed.
Disabling Two Factor Authentication
Disabling two factor authentication also requires the user password.
await authClient.twoFactor.disable({
password: ui.twoFactorPassword.trim(),
})After disable succeeds, the settings UI clears local setup state and refreshes the session.
Last Used Method
The sign in screen uses the Better Auth last login method plugin to show the last used provider next to Google, email, password, or passkey options.
const lastUsedLoginMethod = authClient.getLastUsedLoginMethod()This is a small UX detail, but it reduces repeat sign in friction because returning users can quickly choose the method they used last.
Where To Customize
Use these files first:
src/features/settings/components/settings-account-section/settings-account-section.tsxfor account security UI.src/features/auth/lib/auth-client.tsfor browser auth plugins.src/features/auth/lib/auth.tsfor Better Auth server plugins.src/features/settings/actions/get-passkey-status.action.tsfor checking passkey state.src/features/settings/store/passkey-status.store.tsfor cached passkey hints.
Keep passkey and two factor UI in settings. Keep auth provider configuration in the auth feature.