Multi-Factor Authentication Configuration
Configure TOTP-based multi-factor authentication for enhanced account security.
Add an extra layer of security with multi-factor authentication. Users enable MFA in their security settings and verify with a 6-digit code from their authenticator app on each sign-in.
This page is part of the Authentication documentation.
Overview
MFA (Multi-Factor Authentication) requires users to provide a second factor - a time-based one-time password (TOTP) - in addition to their password. This protects accounts even if passwords are compromised.
Supported authenticator apps:
- Google Authenticator
- Authy
- 1Password
- Microsoft Authenticator
- Any TOTP-compatible app
How TOTP Works
TOTP generates 6-digit codes that change every 30 seconds. The code is derived from:
- A shared secret (stored in the authenticator app)
- The current time
Both your server and the authenticator app generate the same code at the same time, allowing verification without network communication.
Configuration
The MFA plugin is configured in packages/better-auth/src/plugins/two-factor.ts:
import { twoFactor } from 'better-auth/plugins/two-factor';import { env } from '@kit/shared/env';export function createTwoFactorPlugin() { return twoFactor({ issuer: env('NEXT_PUBLIC_PRODUCT_NAME'), });}The issuer appears in authenticator apps to identify which app the code is for (e.g., "MakerKit" or your app name).
MFA Setup Flow
When a user enables MFA:
- User navigates to Security Settings (
/home/settings/security) - User clicks "Enable MFA"
- Server generates a secret and QR code
- User scans QR code with authenticator app
- User enters the current 6-digit code to verify
- Server generates recovery codes
- User saves recovery codes securely
- MFA is enabled for the account
QR Code Display
The QR code contains a TOTP URL in this format:
otpauth://totp/AppName:user@example.com?secret=BASE32SECRET&issuer=AppNameMFA Login Flow
When a user with MFA enabled signs in:
- User enters email and password
- Server verifies credentials
- Server detects MFA is enabled
- User is redirected to
/auth/verify - User enters 6-digit code from authenticator app
- Server verifies the code
- Session is created
- User is signed in
Client-Side Redirect
The client plugin handles MFA redirects automatically:
twoFactorClient({ onTwoFactorRedirect() { const redirect = new URLSearchParams(location.search).get('redirect'); window.location.href = '/auth/verify' + (redirect ? `?redirect=${redirect}` : ''); },}),Recovery Codes
Recovery codes allow users to sign in if they lose access to their authenticator app. Each code can only be used once.
How recovery codes work:
- 8-10 codes are generated when MFA is enabled
- Codes are shown once and must be saved by the user
- Each code can be used instead of a TOTP code
- Used codes are invalidated immediately
- Users can regenerate codes (invalidates all previous codes)
Best practices for users:
- Store codes in a password manager
- Print and store in a secure location
- Never share codes with anyone
- Regenerate if codes may be compromised
Client Usage
Enable MFA
import { authClient } from '@kit/better-auth/client';// Generate secret and QR codeconst { data } = await authClient.twoFactor.enable();// data.totpURI - QR code URI for authenticator apps// data.backupCodes - Recovery codes to save// After user scans QR code, verify with their first codeawait authClient.twoFactor.verifyTOTP({ code: '123456',});Verify During Sign-In
import { authClient } from '@kit/better-auth/client';// Called on /auth/verify page after sign-inawait authClient.twoFactor.verifyTOTP({ code: '123456',});// Or use a recovery codeawait authClient.twoFactor.verifyBackupCode({ code: 'ABCD-1234-EFGH',});Disable MFA
import { authClient } from '@kit/better-auth/client';await authClient.twoFactor.disable({ password: 'userPassword', // Requires password confirmation});Security Settings UI
The security settings page at /home/settings/security provides:
- Enable/Disable MFA - Toggle two-factor authentication
- View Recovery Codes - Show existing recovery codes (requires password)
- Regenerate Recovery Codes - Generate new codes (invalidates old ones)
Common Pitfalls
- Using production authenticator entries for development: Create separate entries for dev and prod to avoid confusion.
- Not saving recovery codes: Users must save recovery codes. Without them, lost authenticator access means account lockout.
- Time sync issues: TOTP requires accurate time. If codes don't work, check server time sync.
- Forgetting to add client plugin: The
twoFactorClientplugin must be inauth-client.tsfor redirects to work. - MFA only works for email/password sign-ins: MFA is not supported for OAuth sign-ins - as it is assumed the user activates MFA in the oAuth provider.
Frequently Asked Questions
Can I require MFA for all users?
What if a user loses their phone?
Can I use SMS-based MFA instead of TOTP?
How many recovery codes are generated?
Next: Adding Plugins →