双重身份验证 (2FA)

通过双重身份验证增强您的应用安全性。

OTP TOTP 备用代码 受信设备

双重身份验证 (2FA) 在用户登录时增加了一道额外的安全步骤。用户不仅需要密码,还需提供第二种验证方式。这使得即使密码泄露,未经授权的人也更难访问账户。

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

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

附加功能包括:

  • 生成备用代码用于账户恢复
  • 启用/禁用 2FA
  • 管理受信设备

安装

将插件添加到您的身份验证配置中

将双重身份验证插件添加到您的身份验证配置,并将您的应用名称指定为发布者。

auth.ts
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"

export const auth = betterAuth({
    // ... 其他配置选项
    appName: "My App", // 提供您的应用名称。它将作为发布者使用。
    plugins: [
        twoFactor() 
    ]
})

迁移数据库

运行迁移或生成模式,为数据库添加必要的字段和表。

npx auth migrate
npx auth generate

详细请参阅模式部分手动添加字段。

添加客户端插件

添加客户端插件,并指定用户需要进行二次验证时的重定向地址。

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",});
Parameters
passwordstringrequired

用户密码

issuerstring

可选的 TOTP URI 发布者。默认是身份验证配置中的 app-name。

启用 2FA 时:

  • 会生成加密的 secretbackupCodes
  • enable 返回 totpURIbackupCodes

注意:在用户验证他们的 TOTP 代码之前,twoFactorEnabled 不会被设置为 true。了解更多关于 TOTP 验证内容请参阅这里。您也可以在插件配置中将 skipVerificationOnEnable 设置为 true,跳过验证。

当前双重身份验证只支持凭证账户(credential accounts)。对于社交账户,默认假设提供商已处理 2FA。

使用 2FA 登录

当启用了 2FA 的用户通过邮箱登录时,响应对象中会包含 twoFactorRedirect 并设置为 true,表示用户需要验证他们的 2FA 代码。

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

import { authClient } from "@/lib/auth-client"

await authClient.signIn.email({
        email: "user@example.com",
        password: "password123",
    },
    {
        async onSuccess(context) {
            if (context.data.twoFactorRedirect) {
                // 处理当前页面上的 2FA 验证
            }
        },
    }
)

使用 onTwoFactorRedirect 配置示例:

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

const authClient = createAuthClient({
    plugins: [
        twoFactorClient({
            onTwoFactorRedirect(){
                // 全局处理 2FA 验证
            },
        }),
    ],
});

关于 auth.api

当您在服务器调用 auth.api.signInEmail 并且用户启用了 2FA 时,返回的对象中会含有 twoFactorRedirect 并设置为 true。这种行为在 TypeScript 中不会被类型推断,可能会导致误导。您可以使用 in 操作符检查是否含有 twoFactorRedirect

sign-in.ts
import { auth } from "@/lib/auth"

const response = await auth.api.signInEmail({
	body: {
		email: "test@test.com",
		password: "test",
	},
});

if ("twoFactorRedirect" in response) {
	// 处理当前页面上的 2FA 验证
}

禁用 2FA

要禁用双重身份验证,调用 twoFactor.disable 并传入用户密码:

POST/two-factor/disable
const { data, error } = await authClient.twoFactor.disable({    password, // required});
Parameters
passwordstringrequired

用户密码

TOTP

TOTP(基于时间的一次性密码)是一种使用时间计数器为每次登录生成唯一密码的算法。每隔固定时间(Better Auth 默认 30 秒)生成一个新密码。它解决了传统密码可能被遗忘、窃取或猜测的问题。虽然 OTP 也解决了一部分问题,但通过短信或邮箱发送 OTP 的可靠性较差(甚至较风险,因为可能带来新的攻击面)。

而 TOTP 是线下生成代码,既安全又便捷。只需在手机安装认证器应用即可。

获取 TOTP URI

启用 2FA 后,可获取 TOTP URI 并向用户展示。该 URI 是服务器基于 secretissuer 生成,用于生成二维码,供用户用认证器扫描。

POST/two-factor/get-totp-uri
const { data, error } = await authClient.twoFactor.getTotpUri({    password, // required});
Parameters
passwordstringrequired

用户密码

示例:React 方式

获取 TOTP URI 后,您可用它生成二维码,供用户用认证器扫描。

import { authClient } from "@/lib/auth-client"
import QRCode from "react-qr-code";

export default function UserCard({ password }: { password: string }){
    const { data: session } = authClient.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

用户输入 2FA 代码后,可调用 twoFactor.verifyTotp 方法进行验证。Better Auth 遵循标准做法,接受当前时间段前后各一个周期内的代码,确保用户即使存在轻微时间偏差也可认证成功。

POST/two-factor/verify-totp
const { data, error } = await authClient.twoFactor.verifyTotp({    code: "012345", // required    trustDevice: true,});
Parameters
codestringrequired

待验证的 otp 代码。

trustDeviceboolean

若为 true,该设备将在 30 天内被信任。期间每次登录均刷新信任期限。

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 }, ctx) {
                    // 发送 otp 给用户
				},
			},
        })
    ]
})

发送 OTP

发送 OTP 通过调用 twoFactor.sendOtp 完成。该函数会触发您在 Better Auth 配置中提供的 sendOTP 实现。

POST/two-factor/send-otp
const { data, error } = await authClient.twoFactor.sendOtp({    trustDevice: true,});if (data) {    // 重定向或提示用户输入代码}
Parameters
trustDeviceboolean

若为 true,该设备将在 30 天内被信任。期间每次登录均刷新信任期限。

验证 OTP

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

POST/two-factor/verify-otp
const { data, error } = await authClient.twoFactor.verifyOtp({    code: "012345", // required    trustDevice: true,});
Parameters
codestringrequired

待验证 otp 代码。

trustDeviceboolean

若为 true,该设备将在 30 天内被信任。期间每次登录均刷新信任期限。

备用代码

备用代码存储于数据库。当用户失去手机或邮箱访问权限时,可用于账户恢复。

生成备用代码

生成用于账户恢复的备用代码:

POST/two-factor/generate-backup-codes
const { data, error } = await authClient.twoFactor.generateBackupCodes({    password, // required});if (data) {    // 显示备用代码给用户}
Parameters
passwordstringrequired

用户密码。

生成备用代码时,旧的备用代码会被删除,生成新的。

使用备用代码

您可允许用户使用备用代码作为账户恢复方式。

POST/two-factor/verify-backup-code
const { data, error } = await authClient.twoFactor.verifyBackupCode({    code: "123456", // required    disableSession: false,    trustDevice: true,});
Parameters
codestringrequired

待验证的备用代码。

disableSessionboolean

若为 true,不会设置会话 cookie。

trustDeviceboolean

若为 true,该设备将在 30 天内被信任。期间每次登录均刷新信任期限。

一旦备用代码被使用,它会从数据库中移除,且不可再次使用。

查看备用代码

要向用户展示备用代码,可在服务器调用 viewBackupCodes,返回备用代码。请确保仅允许拥有刚创建的新鲜会话的用户执行此操作。

const data = await auth.api.viewBackupCodes({    body: {        userId: "user-id",    },});
Parameters
userIdstring | null

要查看备用代码的用户 ID。

受信设备

您可以通过向 verifyTotpverifyOtp 传入 trustDevice 来标记设备为受信设备。

const verify2FA = async (code: string) => {
    const { data, error } = await authClient.twoFactor.verifyTotp({
        code,
        trustDevice: true, // 标记当前设备为受信设备
    })
    if (data) {
        // 2FA 验证成功且设备被标记受信
    }
}

trustDevice 设置为 true,当前设备将在 30 天内被记为受信设备。在此期间,用户通过该设备再次登录时不会被提示进行 2FA 验证。每次成功登录都会刷新该受信期限。

发布者(Issuer)

通过添加 issuer,您可以设置应用名称,该名称会显示在 2FA 应用中。

例如,若用户使用 Google Auth,默认 appName 会显示为 Better Auth。您可如下配置,使其显示为 my-app-name

twoFactor({
    issuer: "my-app-name"
})

模式 (Schema)

该插件需要在 user 表中添加 1 个额外字段,且新增 1 个表用于存储双重身份验证数据。

表:user

Table
字段
类型
描述
twoFactorEnabled
boolean
?
用户是否启用了双重身份验证。

表:twoFactor

Table
字段
类型
描述
id
string
pk
双重身份验证记录的 ID。
userId
string
fk
用户 ID
secret
string
?
用于生成 TOTP 代码的密钥。
backupCodes
string
?
用于恢复账户访问的备用代码。

配置选项

服务器端

twoFactorTable:存储双重身份验证数据的表名。默认值:twoFactor

skipVerificationOnEnable:启用用户的双重身份验证时,跳过验证过程。

Issuer:应用的名称,用于生成 TOTP 代码,会显示在认证器应用中。

TOTP 选项

TOTP 的相关配置。

Prop

Type

OTP 选项

OTP 的相关配置。

Prop

Type

备用代码选项

当用户启用双重身份验证时,备用代码会被生成并存储,用于在用户无法访问手机或邮箱时恢复账户。

Prop

Type

客户端

在客户端使用双重身份验证插件时,需要将其添加到插件列表中。

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" // 处理 2FA 验证重定向
            } 
        }) 
    ]
})

选项

onTwoFactorRedirect:当用户需要验证 2FA 代码时调用的回调函数。您可以用它来重定向用户到 2FA 页面。