two-factor-authentication-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSetting Up Two-Factor Authentication
设置双因素认证
When adding 2FA to your application, configure the plugin with your app name as the issuer. This name appears in authenticator apps when users scan the QR code.
twoFactorts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
appName: "My App", // Used as the default issuer for TOTP
plugins: [
twoFactor({
issuer: "My App", // Optional: override the app name for 2FA specifically
}),
],
});Note: After adding the plugin, run to add the required database fields and tables.
npx @better-auth/cli migrate在你的应用中添加2FA时,请将插件的发行方(issuer)配置为你的应用名称。用户扫描二维码时,该名称会显示在认证器应用中。
twoFactorts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
appName: "My App", // Used as the default issuer for TOTP
plugins: [
twoFactor({
issuer: "My App", // Optional: override the app name for 2FA specifically
}),
],
});注意:添加插件后,请运行来添加所需的数据库字段和表。
npx @better-auth/cli migrateClient-Side Setup
客户端设置
Add the client plugin and configure the redirect behavior for 2FA verification:
ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect() {
window.location.href = "/2fa"; // Redirect to your 2FA verification page
},
}),
],
});添加客户端插件并配置2FA验证的重定向行为:
ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect() {
window.location.href = "/2fa"; // Redirect to your 2FA verification page
},
}),
],
});Enabling 2FA for Users
为用户启用2FA
When a user enables 2FA, require their password for verification. The enable endpoint returns a TOTP URI for QR code generation and backup codes for account recovery.
ts
const enable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.enable({
password,
});
if (data) {
// data.totpURI - Use this to generate a QR code
// data.backupCodes - Display these to the user for safekeeping
}
};Important: The flag on the user is not set to until the user successfully verifies their first TOTP code. This ensures users have properly configured their authenticator app before 2FA is fully active.
twoFactorEnabledtrue当用户启用2FA时,需要验证他们的密码。启用接口会返回一个用于生成二维码的TOTP URI,以及用于账户恢复的备份码。
ts
const enable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.enable({
password,
});
if (data) {
// data.totpURI - Use this to generate a QR code
// data.backupCodes - Display these to the user for safekeeping
}
};重要提示:只有当用户成功验证其第一个TOTP代码后,用户的标志才会设置为。这确保用户在2FA完全激活前已正确配置好他们的认证器应用。
twoFactorEnabledtrueSkipping Initial Verification
跳过初始验证
If you want to enable 2FA immediately without requiring verification, set :
skipVerificationOnEnablets
twoFactor({
skipVerificationOnEnable: true, // Not recommended for most use cases
});Note: This is generally not recommended as it doesn't confirm the user has successfully set up their authenticator app.
如果你想立即启用2FA而无需验证,可以设置:
skipVerificationOnEnablets
twoFactor({
skipVerificationOnEnable: true, // Not recommended for most use cases
});注意:通常不建议这样做,因为它无法确认用户已成功设置好认证器应用。
TOTP (Authenticator App)
TOTP(认证器应用)
TOTP generates time-based codes using an authenticator app (Google Authenticator, Authy, etc.). Codes are valid for 30 seconds by default.
TOTP通过认证器应用(如Google Authenticator、Authy等)生成基于时间的代码。默认情况下,代码有效期为30秒。
Displaying the QR Code
显示二维码
Use the TOTP URI to generate a QR code for users to scan:
tsx
import QRCode from "react-qr-code";
const TotpSetup = ({ totpURI }: { totpURI: string }) => {
return <QRCode value={totpURI} />;
};使用TOTP URI生成供用户扫描的二维码:
tsx
import QRCode from "react-qr-code";
const TotpSetup = ({ totpURI }: { totpURI: string }) => {
return <QRCode value={totpURI} />;
};Verifying TOTP Codes
验证TOTP代码
Better Auth accepts codes from one period before and one after the current time, accommodating minor clock differences between devices:
ts
const verifyTotp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
trustDevice: true, // Optional: remember this device for 30 days
});
};Better Auth接受当前时间段前后各一个周期内的代码,以适应设备间的微小时钟差异:
ts
const verifyTotp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
trustDevice: true, // Optional: remember this device for 30 days
});
};TOTP Configuration Options
TOTP配置选项
ts
twoFactor({
totpOptions: {
digits: 6, // 6 or 8 digits (default: 6)
period: 30, // Code validity period in seconds (default: 30)
},
});ts
twoFactor({
totpOptions: {
digits: 6, // 6 or 8 digits (default: 6)
period: 30, // Code validity period in seconds (default: 30)
},
});OTP (Email/SMS)
OTP(邮件/短信)
OTP sends a one-time code to the user's email or phone. You must implement the function to deliver codes.
sendOTPOTP会将一次性代码发送到用户的邮箱或手机。你必须实现函数来发送代码。
sendOTPConfiguring OTP Delivery
配置OTP发送
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";
export const auth = betterAuth({
plugins: [
twoFactor({
otpOptions: {
sendOTP: async ({ user, otp }, ctx) => {
await sendEmail({
to: user.email,
subject: "Your verification code",
text: `Your code is: ${otp}`,
});
},
period: 5, // Code validity in minutes (default: 3)
digits: 6, // Number of digits (default: 6)
allowedAttempts: 5, // Max verification attempts (default: 5)
},
}),
],
});ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";
export const auth = betterAuth({
plugins: [
twoFactor({
otpOptions: {
sendOTP: async ({ user, otp }, ctx) => {
await sendEmail({
to: user.email,
subject: "Your verification code",
text: `Your code is: ${otp}`,
});
},
period: 5, // Code validity in minutes (default: 3)
digits: 6, // Number of digits (default: 6)
allowedAttempts: 5, // Max verification attempts (default: 5)
},
}),
],
});Sending and Verifying OTP
发送并验证OTP
ts
// Request an OTP to be sent
const sendOtp = async () => {
const { data, error } = await authClient.twoFactor.sendOtp();
};
// Verify the OTP code
const verifyOtp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyOtp({
code,
trustDevice: true,
});
};ts
// Request an OTP to be sent
const sendOtp = async () => {
const { data, error } = await authClient.twoFactor.sendOtp();
};
// Verify the OTP code
const verifyOtp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyOtp({
code,
trustDevice: true,
});
};OTP Storage Security
OTP存储安全性
Configure how OTP codes are stored in the database:
ts
twoFactor({
otpOptions: {
storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
},
});For custom encryption:
ts
twoFactor({
otpOptions: {
storeOTP: {
encrypt: async (token) => myEncrypt(token),
decrypt: async (token) => myDecrypt(token),
},
},
});配置OTP代码在数据库中的存储方式:
ts
twoFactor({
otpOptions: {
storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
},
});自定义加密方式:
ts
twoFactor({
otpOptions: {
storeOTP: {
encrypt: async (token) => myEncrypt(token),
decrypt: async (token) => myDecrypt(token),
},
},
});Backup Codes
备份码
Backup codes provide account recovery when users lose access to their authenticator app or phone. They are generated automatically when 2FA is enabled.
当用户无法访问认证器应用或手机时,备份码可用于账户恢复。启用2FA时会自动生成备份码。
Displaying Backup Codes
显示备份码
Always show backup codes to users when they enable 2FA:
tsx
const BackupCodes = ({ codes }: { codes: string[] }) => {
return (
<div>
<p>Save these codes in a secure location:</p>
<ul>
{codes.map((code, i) => (
<li key={i}>{code}</li>
))}
</ul>
</div>
);
};启用2FA时,务必向用户显示备份码:
tsx
const BackupCodes = ({ codes }: { codes: string[] }) => {
return (
<div>
<p>Save these codes in a secure location:</p>
<ul>
{codes.map((code, i) => (
<li key={i}>{code}</li>
))}
</ul>
</div>
);
};Regenerating Backup Codes
重新生成备份码
When users need new codes, regenerate them (this invalidates all previous codes):
ts
const regenerateBackupCodes = async (password: string) => {
const { data, error } = await authClient.twoFactor.generateBackupCodes({
password,
});
// data.backupCodes contains the new codes
};当用户需要新的备份码时,可重新生成(这会使所有旧代码失效):
ts
const regenerateBackupCodes = async (password: string) => {
const { data, error } = await authClient.twoFactor.generateBackupCodes({
password,
});
// data.backupCodes contains the new codes
};Using Backup Codes for Recovery
使用备份码恢复账户
ts
const verifyBackupCode = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyBackupCode({
code,
trustDevice: true,
});
};Note: Each backup code can only be used once and is removed from the database after successful verification.
ts
const verifyBackupCode = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyBackupCode({
code,
trustDevice: true,
});
};注意:每个备份码只能使用一次,验证成功后会从数据库中删除。
Backup Code Configuration
备份码配置
ts
twoFactor({
backupCodeOptions: {
amount: 10, // Number of codes to generate (default: 10)
length: 10, // Length of each code (default: 10)
storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
},
});ts
twoFactor({
backupCodeOptions: {
amount: 10, // Number of codes to generate (default: 10)
length: 10, // Length of each code (default: 10)
storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
},
});Handling 2FA During Sign-In
登录时处理2FA
When a user with 2FA enabled signs in, the response includes :
twoFactorRedirect: truets
const signIn = async (email: string, password: string) => {
const { data, error } = await authClient.signIn.email(
{
email,
password,
},
{
onSuccess(context) {
if (context.data.twoFactorRedirect) {
// Redirect to 2FA verification page
window.location.href = "/2fa";
}
},
}
);
};当已启用2FA的用户登录时,响应会包含:
twoFactorRedirect: truets
const signIn = async (email: string, password: string) => {
const { data, error } = await authClient.signIn.email(
{
email,
password,
},
{
onSuccess(context) {
if (context.data.twoFactorRedirect) {
// Redirect to 2FA verification page
window.location.href = "/2fa";
}
},
}
);
};Server-Side 2FA Detection
服务端2FA检测
When using on the server, check for 2FA redirect:
auth.api.signInEmailts
const response = await auth.api.signInEmail({
body: {
email: "user@example.com",
password: "password",
},
});
if ("twoFactorRedirect" in response) {
// Handle 2FA verification
}在服务端使用时,检查是否需要重定向到2FA验证:
auth.api.signInEmailts
const response = await auth.api.signInEmail({
body: {
email: "user@example.com",
password: "password",
},
});
if ("twoFactorRedirect" in response) {
// Handle 2FA verification
}Trusted Devices
可信设备
Trusted devices allow users to skip 2FA verification on subsequent sign-ins for a configurable period.
可信设备允许用户在后续登录时跳过2FA验证,有效期可配置。
Enabling Trust on Verification
验证时启用可信设备
Pass when verifying 2FA:
trustDevice: truets
await authClient.twoFactor.verifyTotp({
code: "123456",
trustDevice: true,
});验证2FA时传入:
trustDevice: truets
await authClient.twoFactor.verifyTotp({
code: "123456",
trustDevice: true,
});Configuring Trust Duration
配置信任有效期
ts
twoFactor({
trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days in seconds (default)
});Note: The trust period refreshes on each successful sign-in within the trust window.
ts
twoFactor({
trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days in seconds (default)
});注意:在信任窗口内每次成功登录都会刷新信任周期。
Security Considerations
安全注意事项
Session Management
会话管理
During the 2FA flow:
- User signs in with credentials
- Session cookie is removed (not yet authenticated)
- A temporary two-factor cookie is set (default: 10-minute expiration)
- User verifies via TOTP, OTP, or backup code
- Session cookie is created upon successful verification
Configure the two-factor cookie expiration:
ts
twoFactor({
twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});在2FA流程中:
- 用户使用凭据登录
- 会话Cookie被移除(尚未完成认证)
- 设置一个临时的双因素Cookie(默认有效期10分钟)
- 用户通过TOTP、OTP或备份码完成验证
- 验证成功后创建会话Cookie
配置双因素Cookie的有效期:
ts
twoFactor({
twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});Rate Limiting
速率限制
Better Auth applies built-in rate limiting to all 2FA endpoints (3 requests per 10 seconds). For OTP verification, additional attempt limiting is applied:
ts
twoFactor({
otpOptions: {
allowedAttempts: 5, // Max attempts per OTP code (default: 5)
},
});Better Auth对所有2FA接口应用内置的速率限制(每10秒3次请求)。对于OTP验证,还会应用额外的尝试次数限制:
ts
twoFactor({
otpOptions: {
allowedAttempts: 5, // Max attempts per OTP code (default: 5)
},
});Encryption at Rest
静态加密
- TOTP secrets are encrypted using symmetric encryption with your auth secret
- Backup codes are stored encrypted by default
- OTP codes can be configured for plain, encrypted, or hashed storage
- TOTP密钥使用你的认证密钥通过对称加密存储
- 备份码默认加密存储
- OTP代码可配置为明文、加密或哈希存储
Constant-Time Comparison
恒定时间比较
Better Auth uses constant-time comparison for OTP verification to prevent timing attacks.
Better Auth使用恒定时间比较来验证OTP代码,以防止计时攻击。
Credential Account Requirement
凭据账户要求
Two-factor authentication can only be enabled for credential (email/password) accounts. For social accounts, it's assumed the provider already handles 2FA.
双因素认证仅能为凭据(邮箱/密码)账户启用。对于社交账户,假设提供商已处理2FA。
Disabling 2FA
禁用2FA
Allow users to disable 2FA with password confirmation:
ts
const disable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.disable({
password,
});
};Note: When 2FA is disabled, trusted device records are revoked.
允许用户通过密码确认来禁用2FA:
ts
const disable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.disable({
password,
});
};注意:禁用2FA后,可信设备记录会被撤销。
Complete Configuration Example
完整配置示例
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";
export const auth = betterAuth({
appName: "My App",
plugins: [
twoFactor({
// TOTP settings
issuer: "My App",
totpOptions: {
digits: 6,
period: 30,
},
// OTP settings
otpOptions: {
sendOTP: async ({ user, otp }) => {
await sendEmail({
to: user.email,
subject: "Your verification code",
text: `Your code is: ${otp}`,
});
},
period: 5,
allowedAttempts: 5,
storeOTP: "encrypted",
},
// Backup code settings
backupCodeOptions: {
amount: 10,
length: 10,
storeBackupCodes: "encrypted",
},
// Session settings
twoFactorCookieMaxAge: 600, // 10 minutes
trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days
}),
],
});ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";
export const auth = betterAuth({
appName: "My App",
plugins: [
twoFactor({
// TOTP settings
issuer: "My App",
totpOptions: {
digits: 6,
period: 30,
},
// OTP settings
otpOptions: {
sendOTP: async ({ user, otp }) => {
await sendEmail({
to: user.email,
subject: "Your verification code",
text: `Your code is: ${otp}`,
});
},
period: 5,
allowedAttempts: 5,
storeOTP: "encrypted",
},
// Backup code settings
backupCodeOptions: {
amount: 10,
length: 10,
storeBackupCodes: "encrypted",
},
// Session settings
twoFactorCookieMaxAge: 600, // 10 minutes
trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days
}),
],
});