双因素认证(2FA)
OTP TOTP Backup Codes Trusted Devices
双重身份验证(2FA)在用户登录时增加了一道额外的安全步骤。用户不仅需要使用密码,还需要提供第二种形式的验证。这使得未经授权的人即使获得了密码,也很难访问账户。
【Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.】
该插件提供两种主要方法进行二次身份验证:
【This plugin offers two main methods to do a second factor verification:】
- 一次性密码(OTP):发送到用户邮箱或手机的临时代码。
- TOTP(基于时间的一次性密码):由用户设备上的应用生成的代码。
附加功能包括:
- 生成账户恢复备份码
- 启用/禁用双因素认证(2FA)
- 管理可信设备
安装
【Installation】
将插件添加到你的认证配置
将双因素插件添加到你的身份验证配置中,并将你的应用名称指定为发行者。
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
// ... other config options
appName: "My App", // provide your app name. It'll be used as an issuer.
plugins: [
twoFactor()
]
})Migrate the database
运行迁移或生成架构,以向数据库添加必要的字段和表。
npx @better-auth/cli migratenpx @better-auth/cli generateSee the Schema section to add the fields manually.
添加客户端插件
添加客户端插件,并指定如果用户需要验证第二因素时应重定向到的位置
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
twoFactorClient()
]
})用法
【Usage】
启用双重验证
【Enabling 2FA】
要启用双因素认证,请使用用户的密码和发行者(可选)调用 twoFactor.enable:
【To enable two-factor authentication, call twoFactor.enable with the user's password and issuer (optional):】
const { data, error } = await authClient.twoFactor.enable({ password: "secure-password", // required issuer: "my-app-name",});| Prop | Description | Type |
|---|---|---|
password | The user's password | string |
issuer? | An optional custom issuer for the TOTP URI. Defaults to app-name defined in your auth config. | string |
当启用双重身份验证 (2FA) 时:
- 会生成加密的
secret和backupCodes。 enable会返回totpURI和backupCodes。
【When 2FA is enabled:
- An encrypted
secretandbackupCodesare generated. enablereturnstotpURIandbackupCodes.】
注意:twoFactorEnabled 在用户验证其 TOTP 代码之前不会被设置为 true。关于 TOTP 验证的更多信息,请点击这里。你可以通过在插件配置中将 skipVerificationOnEnable 设置为 true 来跳过验证。
【Note: twoFactorEnabled won’t be set to true until the user verifies their TOTP code. Learn more about verifying TOTP here. You can skip verification by setting skipVerificationOnEnable to true in your plugin config.】
两步验证目前仅可用于凭证账户。对于社交账户,假定提供商已处理了两步验证。
使用两步验证登录
【Sign In with 2FA】
当启用了双因素认证(2FA)的用户尝试通过电子邮件登录时,响应对象将包含 twoFactorRedirect 并设置为 true。这表示用户需要验证他们的 2FA 代码。
【When a user with 2FA enabled tries to sign in via email, the response object will contain twoFactorRedirect set to true. This indicates that the user needs to verify their 2FA code.】
你可以在 onSuccess 回调中处理这个,或者在插件配置中提供 onTwoFactorRedirect 回调。
【You can handle this in the onSuccess callback or by providing a onTwoFactorRedirect callback in the plugin config.】
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
},
{
async onSuccess(context) {
if (context.data.twoFactorRedirect) {
// Handle the 2FA verification in place
}
},
}
)使用 onTwoFactorRedirect 配置:
【Using the onTwoFactorRedirect config:】
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect(){
// Handle the 2FA verification globally
},
}),
],
});使用 auth.api
当你在服务器上调用 auth.api.signInEmail 时,如果用户启用了两步验证,它会返回一个对象,其中 twoFactorRedirect 被设置为 true。TypeScript 并不会推断出这种行为,这可能会导致误解。你可以使用 in 来检查 twoFactorRedirect 是否被设置为 true。
【When you call auth.api.signInEmail on the server, and the user has 2FA enabled, it will return an object where twoFactorRedirect is set to true. This behavior isn’t inferred in TypeScript, which can be misleading. You can check using in instead to check if twoFactorRedirect is set to true.】
const response = await auth.api.signInEmail({
body: {
email: "test@test.com",
password: "test",
},
});
if ("twoFactorRedirect" in response) {
// Handle the 2FA verification in place
}禁用两步验证
【Disabling 2FA】
要禁用双因素认证,请使用用户的密码调用 twoFactor.disable:
【To disable two-factor authentication, call twoFactor.disable with the user's password:】
const { data, error } = await authClient.twoFactor.disable({ password, // required});| Prop | Description | Type |
|---|---|---|
password | The user's password | string |
TOTP
TOTP(基于时间的一次性密码)是一种算法,利用时间作为计数器为每次登录生成唯一密码。每过固定时间间隔(Better Auth 默认是 30 秒),就会生成一个新密码。这解决了传统密码的几个问题:密码可能被遗忘、被盗或被猜到。一次性密码(OTP)解决了其中的一些问题,但通过短信或电子邮件传送可能不可靠(甚至存在风险,因为这可能开启新的攻击途径)。
【TOTP (Time-Based One-Time Password) is an algorithm that generates a unique password for each login attempt using time as a counter. Every fixed interval (Better Auth defaults to 30 seconds), a new password is generated. This addresses several issues with traditional passwords: they can be forgotten, stolen, or guessed. OTPs solve some of these problems, but their delivery via SMS or email can be unreliable (or even risky, considering it opens new attack vectors).】
然而,TOTP 是离线生成验证码的,因此既安全又方便。你只需要在手机上安装一个认证器应用即可。
【TOTP, however, generates codes offline, making it both secure and convenient. You just need an authenticator app on your phone.】
获取 TOTP URI
【Getting TOTP URI】
启用双因素认证(2FA)后,你可以获取 TOTP URI 并显示给用户。这个 URI 由服务器使用 secret 和 issuer 生成,可用于生成二维码,供用户使用其认证器应用扫描。
【After enabling 2FA, you can get the TOTP URI to display to the user. This URI is generated by the server using the secret and issuer and can be used to generate a QR code for the user to scan with their authenticator app.】
const { data, error } = await authClient.twoFactor.getTotpUri({ password, // required});| Prop | Description | Type |
|---|---|---|
password | The user's password | string |
示例:使用 React
一旦你拥有了 TOTP URI,就可以使用它生成二维码,供用户用他们的身份验证器应用扫描。
【Once you have the TOTP URI, you can use it to generate a QR code for the user to scan with their authenticator app.】
import QRCode from "react-qr-code";
export default function UserCard({ password }: { password: string }){
const { data: session } = client.useSession();
const { data: qr } = useQuery({
queryKey: ["two-factor-qr"],
queryFn: async () => {
const res = await authClient.twoFactor.getTotpUri({ password });
return res.data;
},
enabled: !!session?.user.twoFactorEnabled,
});
return (
<QRCode value={qr?.totpURI || ""} />
)
}默认情况下,TOTP 的发行者会设置为身份验证配置中提供的应用名称,如果未提供,则默认为 Better Auth。你可以通过在插件配置中传递 issuer 来覆盖此设置。
验证 TOTP
【Verifying TOTP】
在用户输入他们的双因素认证(2FA)代码后,你可以使用 twoFactor.verifyTotp 方法进行验证。Better Auth 遵循标准做法,接受当前代码前后一个时间周期的 TOTP 代码,以确保用户即使在其端存在轻微的时间延迟,也能完成身份验证。
【After the user has entered their 2FA code, you can verify it using twoFactor.verifyTotp method. Better Auth follows standard practice by accepting TOTP codes from one period before and one after the current code, ensuring users can authenticate even with minor time delays on their end.】
const { data, error } = await authClient.twoFactor.verifyTotp({ code: "012345", // required trustDevice: true,});| Prop | Description | Type |
|---|---|---|
code | The otp code to verify. | string |
trustDevice? | If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. | boolean |
一次性密码
【OTP】
一次性密码(OTP)类似于时间一次性密码(TOTP),但它是生成一个随机代码并发送到用户的邮箱或手机。
【OTP (One-Time Password) is similar to TOTP but a random code is generated and sent to the user's email or phone.】
在使用 OTP 验证第二因素之前,你需要在你的 Better Auth 实例中配置 sendOTP。此函数负责将 OTP 发送到用户的电子邮件、调用或你的应用支持的任何其他方式。
【Before using OTP to verify the second factor, you need to configure sendOTP in your Better Auth instance. This function is responsible for sending the OTP to the user's email, phone, or any other method supported by your application.】
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
twoFactor({
otpOptions: {
async sendOTP({ user, otp }, ctx) {
// send otp to user
},
},
})
]
})发送验证码
【Sending OTP】
发送一次性密码(OTP)是通过调用 twoFactor.sendOtp 函数完成的。该函数会触发你在 Better Auth 配置中提供的 sendOTP 实现。
【Sending an OTP is done by calling the twoFactor.sendOtp function. This function will trigger your sendOTP implementation that you provided in the Better Auth configuration.】
const { data, error } = await authClient.twoFactor.sendOtp({ trustDevice: true,});if (data) { // redirect or show the user to enter the code}| Prop | Description | Type |
|---|---|---|
trustDevice? | If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. | boolean |
验证一次性密码
【Verifying OTP】
用户输入验证码后,你可以进行验证
【After the user has entered their OTP code, you can verify it】
const { data, error } = await authClient.twoFactor.verifyOtp({ code: "012345", // required trustDevice: true,});| Prop | Description | Type |
|---|---|---|
code | The otp code to verify. | string |
trustDevice? | If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. | boolean |
备份代码
【Backup Codes】
备份代码会被生成并存储在数据库中。如果用户丢失了手机或邮箱的访问权限,可以使用这些代码来恢复账户访问。
【Backup codes are generated and stored in the database. This can be used to recover access to the account if the user loses access to their phone or email.】
生成备份代码
【Generating Backup Codes】
生成账户恢复用的备用码:
【Generate backup codes for account recovery:】
const { data, error } = await authClient.twoFactor.generateBackupCodes({ password, // required});if (data) { // Show the backup codes to the user}| Prop | Description | Type |
|---|---|---|
password | The users password. | string |
当你生成备用代码时,旧的备用代码将被删除,并生成新的备用代码。
使用备份代码
【Using Backup Codes】
你现在可以允许用户提供备份码作为账户恢复方法。
【You can now allow users to provide a backup code as an account recovery method.】
const { data, error } = await authClient.twoFactor.verifyBackupCode({ code: "123456", // required disableSession: false, trustDevice: true,});| Prop | Description | Type |
|---|---|---|
code | A backup code to verify. | string |
disableSession? | If true, the session cookie will not be set. | boolean |
trustDevice? | If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. | boolean |
一旦备份代码被使用,它将从数据库中删除,无法再次使用。
查看备份代码
【Viewing Backup Codes】
要向用户显示备份代码,你可以在服务器上调用 viewBackupCodes。这将会在响应中返回备份代码。你应该仅在用户拥有一个新会话 - 即刚创建的会话 - 时执行此操作。
【To display the backup codes to the user, you can call viewBackupCodes on the server. This will return the backup codes in the response. You should only do this if the user has a fresh session - a session that was just created.】
const data = await auth.api.viewBackupCodes({ body: { userId: "user-id", },});| Prop | Description | Type |
|---|---|---|
userId? | The user ID to view all backup codes. | string | null |
信任的设备
【Trusted Devices】
你可以通过向 verifyTotp 或 verifyOtp 传递 trustDevice 来将设备标记为受信任。
【You can mark a device as trusted by passing trustDevice to verifyTotp or verifyOtp.】
const verify2FA = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
trustDevice: true, // Mark this device as trusted
})
if (data) {
// 2FA verified and device trusted
}
}当 trustDevice 设置为 true 时,当前设备将在 30 天内被记住。在此期间,用户在此设备上的后续登录不会被要求进行双因素认证(2FA)。每次用户成功登录时,信任期限都会刷新。
【When trustDevice is set to true, the current device will be remembered for 30 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.】
发行人
【Issuer】
通过添加 issuer,你可以为 2FA 应用设置你的应用名称。
【By adding an issuer you can set your application name for the 2fa application.】
例如,如果你的用户使用 Google 身份验证,默认的 appName 将显示为 Better Auth。但是,通过使用以下代码,它将显示为 my-app-name。
【For example, if your user uses Google Auth, the default appName will show up as Better Auth. However, by using the following code, it will show up as my-app-name.】
twoFactor({
issuer: "my-app-name"
})架构
【Schema】
该插件需要在 user 表中增加一个字段,并增加一个表来存储两步验证数据。
【The plugin requires 1 additional field in the user table and 1 additional table to store the two factor authentication data.】
表:user
【Table: user】
| Field Name | Type | Key | Description |
|---|---|---|---|
| twoFactorEnabled | boolean | Whether two factor authentication is enabled for the user. |
表格:twoFactor
【Table: twoFactor】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | The ID of the two factor authentication. | |
| userId | string | The ID of the user | |
| secret | string | The secret used to generate the TOTP code. | |
| backupCodes | string | The backup codes used to recover access to the account if the user loses access to their phone or email. |
选项
【Options】
服务器
【Server】
twoFactorTable:存储双因素认证数据的表名称。默认值:twoFactor。
skipVerificationOnEnable:在为用户启用双重验证之前跳过验证过程。
发行者:发行者是你的应用名称。它用于生成 TOTP 代码,并会显示在身份验证器应用中。
TOTP 选项
这些是 TOTP 的选项。
【these are options for TOTP.】
Prop
Type
一次性密码选项
这些是一次性密码的选项。
【these are options for OTP.】
Prop
Type
备份代码选项
当用户启用两步验证时,会生成备份代码并存储在数据库中。如果用户无法访问他们的手机或电子邮件,这些代码可用于恢复账户访问。
【backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.】
Prop
Type
客户端
【Client】
要在客户端使用双因素插件,你需要将其添加到你的插件列表中。
【To use the two factor plugin in the client, you need to add it on your plugins list.】
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect(){
window.location.href = "/2fa" // Handle the 2FA verification redirect
}
})
]
})选项
onTwoFactorRedirect:当用户需要验证他们的双因素认证(2FA)代码时,将调用的回调函数。这可以用来将用户重定向到双因素认证页面。