Multi-Factor Authentication
TOTP-based two-factor authentication with authenticator apps. Users can enable MFA from their security settings.
MFA Setup and Usage
How users enable and use MFA.
Multi-Factor Authentication (MFA) adds an extra layer of security by requiring users to enter a time-based one-time password (TOTP) from an authenticator app after signing in with their email and password.
How MFA Works
MFA in the kit uses TOTP (Time-based One-Time Password), compatible with authenticator apps like:
- Google Authenticator
- Authy
- 1Password
- Microsoft Authenticator
- Any TOTP-compatible app
When MFA is enabled:
- User signs in with email/password
- Better Auth detects MFA is enabled for this user
- User is redirected to
/auth/verify - User enters the 6-digit code from their authenticator app
- If valid, session is created and user proceeds to dashboard
Enabling MFA
Users enable MFA from their security settings page.
Route: /settings/security

Setup Process
- User clicks "Enable MFA" button
- A QR code is displayed
- User scans the QR code with their authenticator app
- User enters the initial verification code to confirm setup
- MFA is now active on their account
The QR code contains the TOTP secret and account information in a standard otpauth:// URI format that authenticator apps recognize.
Backup Codes
When MFA is enabled, users should be prompted to save backup codes. These one-time codes can be used if the user loses access to their authenticator app.
Verification Flow

Route: /auth/verify
The verification page shows a simple form for entering the 6-digit code. The code changes every 30 seconds in the authenticator app.
Client-Side Redirect
The auth client handles the redirect to verification automatically:
packages/better-auth/src/auth-client.ts
twoFactorClient({ onTwoFactorRedirect() { const redirect = new URLSearchParams(location.search).get('redirect'); window.location.href = '/auth/verify' + (redirect ? `?redirect=${redirect}` : ''); },}),Verification Component
The MFA challenge is handled by MultiFactorChallengeContainer:
packages/auth/src/components/multi-factor-challenge-container.tsx
<MultiFactorChallengeContainer paths={{ signIn: '/auth/sign-in', }}/>Configuration
MFA is enabled through the Better Auth two-factor plugin:
packages/better-auth/src/plugins/two-factor.ts
import { twoFactor } from 'better-auth/plugins/two-factor';export function createTwoFactorPlugin() { return twoFactor({ issuer: env('NEXT_PUBLIC_PRODUCT_NAME'), // Shows in authenticator app });}The issuer appears in the authenticator app alongside the account, helping users identify which service the code is for.
Programmatic MFA
Enable MFA
'use client';import { authClient } from '@kit/better-auth/client';async function enableMFA() { const result = await authClient.twoFactor.enable(); if (result.error) { console.error(result.error.message); return; } // result.data contains the TOTP secret and QR code URL return result.data;}Verify MFA Code
'use client';import { authClient } from '@kit/better-auth/client';async function verifyMFACode(code: string) { const result = await authClient.twoFactor.verifyTotp({ code, }); if (result.error) { console.error(result.error.message); return; } // MFA verified, session created window.location.href = '/dashboard';}Disable MFA
'use client';import { authClient } from '@kit/better-auth/client';async function disableMFA(code: string) { const result = await authClient.twoFactor.disable({ code, // Requires current TOTP code to disable }); if (result.error) { console.error(result.error.message); return; } // MFA disabled}MFA and Other Auth Methods
Magic Links
Magic link authentication bypasses MFA. If you require MFA for all users, consider disabling magic links:
apps/web/.env.local
NEXT_PUBLIC_AUTH_MAGIC_LINK=falseThis is by design since the email itself serves as a second factor (possession of the email account).
Social Providers
Social provider authentication bypasses MFA. The assumption is that social providers (Google, GitHub) have their own security measures. If you need stricter security, you may want to limit sign-in methods.
Password-Only MFA
Only email/password authentication enforces MFA. This provides a balance between security and convenience:
| Auth Method | MFA Required? |
|---|---|
| Email/Password | Yes (if enabled) |
| Magic Link | No |
| Google OAuth | No |
| GitHub OAuth | No |
Security Considerations
Rate Limiting
MFA verification attempts are rate-limited to prevent brute-force attacks. After several failed attempts, the user may need to wait before trying again.
Session Security
When MFA verification succeeds, a new session is created. The session inherits the same security properties (HTTP-only cookies, secure flag in production).
Recovery
If users lose access to their authenticator app:
- They can use backup codes (if saved)
- An admin can disable MFA for their account
- They can contact support for manual verification
Common Issues
"Invalid code" Error
- Code has expired (they change every 30 seconds)
- Device time is out of sync with server time
- Wrong account selected in authenticator app
QR Code Won't Scan
- Ensure good lighting and camera focus
- Try entering the secret manually in the authenticator app
- Check that the QR code is fully visible
MFA Locked Out
If a user is locked out:
- Check for backup codes
- Admin can query the database and update the user's MFA status
- Consider implementing an account recovery flow
Frequently Asked Questions
Is MFA required for all users?
What authenticator apps are supported?
Does MFA work with magic links?
Can I require MFA for certain roles?
How do I reset MFA for a user?
Previous: Session Handling ← | Next: Personal Accounts →