双因素认证 (2FA)
OTP
TOTP
备用验证码
可信设备
双因素认证(2FA)在用户登录时增加了一个额外的安全步骤。除了使用密码外,用户还需要提供第二种验证方式。这使得未经授权的人员即使获取了密码,也难以访问账户。
该插件提供了两种主要的二次验证方法:
- OTP(一次性密码):发送到用户邮箱或手机的临时验证码。
- TOTP(基于时间的一次性密码):由用户设备上的应用程序生成的动态验证码。
附加功能包括:
- 生成用于账户恢复的备用验证码
- 启用/禁用双因素认证
- 管理可信设备
安装
将插件添加到认证配置中
将双因素认证插件添加到您的认证配置中,并指定您的应用名称作为发行者。
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
// ... 其他配置选项
appName: "My App", // 提供您的应用名称,它将作为发行者使用。
plugins: [
twoFactor()
]
})
迁移数据库
运行迁移或生成模式,以向数据库添加必要的字段和表。
npx @better-auth/cli migrate
npx @better-auth/cli generate
请参阅 Schema 部分以手动添加字段。
添加客户端插件
添加客户端插件,并指定用户在需要验证第二因素时应被重定向的位置。
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
twoFactorClient()
]
})
使用方法
启用双因素认证(2FA)
要启用双因素认证,请使用用户的密码和发行方(可选)调用 twoFactor.enable
:
const { data, error } = await authClient.twoFactor.enable({ password: "secure-password", // required issuer: "my-app-name",});
属性 | 描述 | 类型 |
---|---|---|
password | 用户密码 | string |
issuer? | 可选的 TOTP URI 自定义发行方。默认为您在认证配置中定义的应用程序名称。 | string |
启用 2FA 时:
- 会生成加密的
secret
(密钥)和backupCodes
(备份代码) enable
方法会返回totpURI
和backupCodes
注意:在用户验证其 TOTP 代码之前,twoFactorEnabled
不会被设置为 true
。了解更多关于验证 TOTP 的信息请点击此处。您可以在插件配置中将 skipVerificationOnEnable
设置为 true 来跳过验证。
目前双因素认证仅适用于凭证账户。对于社交账户,假定提供商已处理 2FA。
启用双因素认证(2FA)的登录流程
当已启用双因素认证的用户尝试通过邮箱登录时,响应对象将包含 twoFactorRedirect
字段且其值为 true
。这表示用户需要验证其双因素认证代码。
您可以在 onSuccess
回调中处理此情况,或者在插件配置中提供 onTwoFactorRedirect
回调。
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
},
{
async onSuccess(context) {
if (context.data.twoFactorRedirect) {
// 在此处处理双因素认证验证
}
},
}
)
使用 onTwoFactorRedirect
配置:
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect(){
// 全局处理双因素认证验证
},
}),
],
});
使用 auth.api
时
当您在服务器端调用 auth.api.signInEmail
,且用户已启用双因素认证时,将返回一个 twoFactorRedirect
设置为 true
的对象。TypeScript 无法推断此行为,这可能会引起误解。您可以使用 in
操作符来检查 twoFactorRedirect
是否设置为 true
。
const response = await auth.api.signInEmail({
body: {
email: "test@test.com",
password: "test",
},
});
if ("twoFactorRedirect" in response) {
// 在此处处理双因素认证验证
}
禁用双因素认证
要禁用双因素认证,请调用 twoFactor.disable
并传入用户密码:
const { data, error } = await authClient.twoFactor.disable({ password, // required});
属性 | 描述 | 类型 |
---|---|---|
password | 用户密码 | string |
TOTP(基于时间的一次性密码)
TOTP(Time-Based One-Time Password)是一种基于时间计数器的算法,每次登录尝试都会生成一个唯一的密码。每隔固定时间间隔(Better Auth 默认为 30 秒),就会生成一个新的密码。这解决了传统密码的若干问题:密码可能被遗忘、被盗或猜测。一次性密码(OTP)解决了其中一些问题,但通过短信或电子邮件发送这些密码可能不可靠(甚至存在风险,因为这可能带来新的攻击途径)。
然而,TOTP 可以离线生成代码,既安全又方便。您只需在手机上安装一个身份验证器应用即可。
获取 TOTP URI
启用双因素认证(2FA)后,您可以获取 TOTP URI 并展示给用户。该 URI 由服务器使用 secret
和 issuer
生成,可用于生成二维码供用户使用身份验证器应用扫描。
const { data, error } = await authClient.twoFactor.getTotpUri({ password, // required});
属性 | 描述 | 类型 |
---|---|---|
password | 用户密码 | string |
示例:使用 React
获取 TOTP URI 后,您可以使用它生成二维码,供用户使用身份验证器应用扫描。
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 的签发者(issuer)设置为认证配置中提供的应用名称,如果未提供,则设置为 Better Auth
。您可以通过在插件配置中传递 issuer
来覆盖此设置。
验证 TOTP
当用户输入其双因素认证(2FA)代码后,您可以使用 twoFactor.verifyTotp
方法进行验证。Better Auth
遵循标准实践,接受当前代码前后一个时间段的 TOTP 代码,确保即使用户端存在微小的时间延迟也能成功认证。
const { data, error } = await authClient.twoFactor.verifyTotp({ code: "012345", // required trustDevice: true,});
属性 | 描述 | 类型 |
---|---|---|
code | 需要验证的 otp 代码。 | string |
trustDevice? | 如果为 true,该设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态。 | boolean |
OTP
OTP(一次性密码)与 TOTP 类似,但它是随机生成的代码,并通过电子邮件或手机发送给用户。
在使用 OTP 验证第二因素之前,您需要在 Better Auth 实例中配置 sendOTP
函数。该函数负责将 OTP 发送到用户的电子邮件、手机或您的应用程序支持的任何其他方式。
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
twoFactor({
otpOptions: {
async sendOTP({ user, otp }, request) {
// 向用户发送 otp
},
},
})
]
})
发送 OTP
通过调用 twoFactor.sendOtp
函数来发送 OTP。此函数将触发您在 Better Auth 配置中提供的 sendOTP 实现。
const { data, error } = await authClient.twoFactor.sendOtp({ trustDevice: true,});if (data) { // 重定向或提示用户输入代码}
属性 | 描述 | 类型 |
---|---|---|
trustDevice? | 如果为 true,该设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态。 | boolean |
验证 OTP
用户输入 OTP 代码后,您可以进行验证
const { data, error } = await authClient.twoFactor.verifyOtp({ code: "012345", // required trustDevice: true,});
属性 | 描述 | 类型 |
---|---|---|
code | 需要验证的 otp 代码 | string |
trustDevice? | 如果为 true,设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态 | boolean |
备份代码
备份代码会在数据库中生成和存储。当用户无法访问手机或邮箱时,可以使用备份代码恢复账户访问权限。
生成备份代码
为账户恢复生成备份代码:
const { data, error } = await authClient.twoFactor.generateBackupCodes({ password, // required});if (data) { // 向用户显示备份代码}
属性 | 描述 | 类型 |
---|---|---|
password | 用户密码 | string |
当您生成备份代码时,旧的备份代码将被删除,新的备份代码将生成。
使用备份代码
您现在可以允许用户提供备份代码作为账户恢复方法。
const { data, error } = await authClient.twoFactor.verifyBackupCode({ code: "123456", // required disableSession: false, trustDevice: true,});
属性 | 描述 | 类型 |
---|---|---|
code | 需要验证的备份代码 | string |
disableSession? | 如果为 true,将不会设置会话 cookie | boolean |
trustDevice? | 如果为 true,设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态 | boolean |
备份代码一旦使用,将从数据库中移除且无法再次使用。
查看备份代码
要向用户显示备份代码,您可以在服务器端调用 viewBackupCodes
。这将在响应中返回备份代码。您应该仅在用户拥有新会话(刚刚创建的会话)时使用此功能。
const data = await auth.api.viewBackupCodes({ body: { userId: "user-id", },});
属性 | 描述 | 类型 |
---|---|---|
userId? | 要查看所有备份代码的用户 ID。 | string | null |
受信任设备
您可以通过向 verifyTotp
或 verifyOtp
传递 trustDevice
来将设备标记为受信任。
const verify2FA = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
callbackURL: "/dashboard",
trustDevice: true // 将此设备标记为受信任
})
if (data) {
// 2FA 验证通过且设备受信任
}
}
当 trustDevice
设置为 true
时,当前设备将被记住 60 天。在此期间,用户在此设备上后续登录时将不会被提示进行 2FA 验证。信任期限会在用户每次成功登录时刷新。
发行者
通过添加 issuer
,您可以为 2FA 应用程序设置您的应用程序名称。
例如,如果您的用户使用 Google Authenticator,默认的应用名称将显示为 Better Auth
。但是,通过使用以下代码,它将显示为 my-app-name
。
twoFactor({
issuer: "my-app-name"
})
数据库结构
该插件需要在 user
表中添加 1 个额外字段,并需要 1 个额外的表来存储双因素认证数据。
字段名称 | 类型 | Key | 描述 |
---|---|---|---|
twoFactorEnabled | boolean | 用户是否启用了双因素认证。 |
表:twoFactor
字段名称 | 类型 | Key | 描述 |
---|---|---|---|
id | string | 双因素认证的 ID。 | |
userId | string | 用户的 ID | |
secret | string | 用于生成 TOTP 代码的密钥。 | |
backupCodes | string | 用于在用户无法访问手机或邮箱时恢复账户访问权限的备用代码。 |
配置选项
服务器端
twoFactorTable:存储双因素认证数据的表名。默认值:twoFactor
。
skipVerificationOnEnable:在为用户启用双因素认证之前跳过验证过程。
Issuer:发行者(Issuer)是您的应用程序名称。它用于生成 TOTP 代码,并会在认证器应用中显示。
TOTP 选项
这些是 TOTP(基于时间的一次性密码)的配置选项。
Prop | Type | Default |
---|---|---|
digits? | number | 6 |
period? | number | 30 |
OTP 选项
这些是 OTP(一次性密码)的配置选项。
Prop | Type | Default |
---|---|---|
sendOTP? | function | - |
period? | number | 3 |
storeOTP? | string | plain |
备份代码选项
当用户启用双因素认证时,系统会生成备份代码并存储在数据库中。如果用户无法访问手机或邮箱,可使用备份代码恢复账户访问权限。
Prop | Type | Default |
---|---|---|
amount? | number | 10 |
length? | number | 10 |
customBackupCodesGenerate? | function | - |
storeBackupCodes? | string | plain |
客户端
要在客户端使用双因素认证插件,您需要将其添加到插件列表中。
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
twoFactorClient({
onTwoFactorRedirect(){
window.location.href = "/2fa" // 处理双因素认证验证的重定向
}
})
]
})
选项
onTwoFactorRedirect
: 当用户需要验证其双因素认证代码时将调用的回调函数。可用于将用户重定向到双因素认证页面。