双因素认证 (2FA)

OTP TOTP 备用验证码 可信设备

双因素认证(2FA)在用户登录时增加了一个额外的安全步骤。除了使用密码外,用户还需要提供第二种验证方式。这使得未经授权的人员即使获取了密码,也难以访问账户。

该插件提供了两种主要的二次验证方法:

  1. OTP(一次性密码):发送到用户邮箱或手机的临时验证码。
  2. TOTP(基于时间的一次性密码):由用户设备上的应用程序生成的动态验证码。

附加功能包括:

  • 生成用于账户恢复的备用验证码
  • 启用/禁用双因素认证
  • 管理可信设备

安装

将插件添加到认证配置中

将双因素认证插件添加到您的认证配置中,并指定您的应用名称作为发行者。

auth.ts
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 部分以手动添加字段。

添加客户端插件

添加客户端插件,并指定用户在需要验证第二因素时应被重定向的位置。

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [
        twoFactorClient()
    ]
})

使用方法

启用双因素认证(2FA)

要启用双因素认证,请使用用户的密码和发行方(可选)调用 twoFactor.enable

POST
/two-factor/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 方法会返回 totpURIbackupCodes

注意:在用户验证其 TOTP 代码之前,twoFactorEnabled 不会被设置为 true。了解更多关于验证 TOTP 的信息请点击此处。您可以在插件配置中将 skipVerificationOnEnable 设置为 true 来跳过验证。

目前双因素认证仅适用于凭证账户。对于社交账户,假定提供商已处理 2FA。

启用双因素认证(2FA)的登录流程

当已启用双因素认证的用户尝试通过邮箱登录时,响应对象将包含 twoFactorRedirect 字段且其值为 true。这表示用户需要验证其双因素认证代码。

您可以在 onSuccess 回调中处理此情况,或者在插件配置中提供 onTwoFactorRedirect 回调。

sign-in.tsx
await authClient.signIn.email({
        email: "user@example.com",
        password: "password123",
    },
    {
        async onSuccess(context) {
            if (context.data.twoFactorRedirect) {
                // 在此处处理双因素认证验证
            }
        },
    }
)

使用 onTwoFactorRedirect 配置:

sign-in.ts
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 并传入用户密码:

POST
/two-factor/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 由服务器使用 secretissuer 生成,可用于生成二维码供用户使用身份验证器应用扫描。

POST
/two-factor/get-totp-uri
const { data, error } = await authClient.twoFactor.getTotpUri({    password, // required});
属性描述类型
password
用户密码
string

示例:使用 React

获取 TOTP URI 后,您可以使用它生成二维码,供用户使用身份验证器应用扫描。

user-card.tsx
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 代码,确保即使用户端存在微小的时间延迟也能成功认证。

POST
/two-factor/verify-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 发送到用户的电子邮件、手机或您的应用程序支持的任何其他方式。

auth.ts
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 实现。

POST
/two-factor/send-otp
const { data, error } = await authClient.twoFactor.sendOtp({    trustDevice: true,});if (data) {    // 重定向或提示用户输入代码}
属性描述类型
trustDevice?
如果为 true,该设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态。
boolean

验证 OTP

用户输入 OTP 代码后,您可以进行验证

POST
/two-factor/verify-otp
const { data, error } = await authClient.twoFactor.verifyOtp({    code: "012345", // required    trustDevice: true,});
属性描述类型
code
需要验证的 otp 代码
string
trustDevice?
如果为 true,设备将被信任 30 天。在此期间内的每次登录请求都会刷新信任状态
boolean

备份代码

备份代码会在数据库中生成和存储。当用户无法访问手机或邮箱时,可以使用备份代码恢复账户访问权限。

生成备份代码

为账户恢复生成备份代码:

POST
/two-factor/generate-backup-codes
const { data, error } = await authClient.twoFactor.generateBackupCodes({    password, // required});if (data) {    // 向用户显示备份代码}
属性描述类型
password
用户密码
string

当您生成备份代码时,旧的备份代码将被删除,新的备份代码将生成。

使用备份代码

您现在可以允许用户提供备份代码作为账户恢复方法。

POST
/two-factor/verify-backup-code
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。这将在响应中返回备份代码。您应该仅在用户拥有新会话(刚刚创建的会话)时使用此功能。

GET
/two-factor/view-backup-codes
const data = await auth.api.viewBackupCodes({    body: {        userId: "user-id",    },});
属性描述类型
userId?
要查看所有备份代码的用户 ID。
string | null

受信任设备

您可以通过向 verifyTotpverifyOtp 传递 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描述
twoFactorEnabledboolean用户是否启用了双因素认证。

表:twoFactor

字段名称类型Key描述
idstring双因素认证的 ID。
userIdstring用户的 ID
secretstring用于生成 TOTP 代码的密钥。
backupCodesstring用于在用户无法访问手机或邮箱时恢复账户访问权限的备用代码。

配置选项

服务器端

twoFactorTable:存储双因素认证数据的表名。默认值:twoFactor

skipVerificationOnEnable:在为用户启用双因素认证之前跳过验证过程。

Issuer:发行者(Issuer)是您的应用程序名称。它用于生成 TOTP 代码,并会在认证器应用中显示。

TOTP 选项

这些是 TOTP(基于时间的一次性密码)的配置选项。

PropTypeDefault
digits?
number
6
period?
number
30

OTP 选项

这些是 OTP(一次性密码)的配置选项。

PropTypeDefault
sendOTP?
function
-
period?
number
3
storeOTP?
string
plain

备份代码选项

当用户启用双因素认证时,系统会生成备份代码并存储在数据库中。如果用户无法访问手机或邮箱,可使用备份代码恢复账户访问权限。

PropTypeDefault
amount?
number
10
length?
number
10
customBackupCodesGenerate?
function
-
storeBackupCodes?
string
plain

客户端

要在客户端使用双因素认证插件,您需要将其添加到插件列表中。

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"

const authClient =  createAuthClient({
    plugins: [
        twoFactorClient({ 
            onTwoFactorRedirect(){ 
                window.location.href = "/2fa" // 处理双因素认证验证的重定向
            } 
        }) 
    ]
})

选项

onTwoFactorRedirect: 当用户需要验证其双因素认证代码时将调用的回调函数。可用于将用户重定向到双因素认证页面。