双重身份验证 (2FA)

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

OTP TOTP 备用代码 受信设备

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

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

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

附加功能包括:

  • 生成备用代码用于账户恢复

  • 启用/禁用 2FA

  • 管理受信设备

  • 生成备用代码用于账户恢复

  • 启用/禁用 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

参见 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。如果启用了 allowPasswordless,对于没有凭证账户的用户可以省略密码。

POST/two-factor/enable
const { data, error } = await authClient.twoFactor.enable({    password: "secure-password",    issuer: "my-app-name",});
Parameters
passwordstring

用户的密码(凭证账户必填)

issuerstring

可选的用于 TOTP URI 的自定义颁发者。默认为认证配置中定义的 app-name。

启用 2FA 后:

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

注意:在用户验证其 TOTP 代码之前,twoFactorEnabled 不会被设置为 true。有关在 TOTP 部分 验证 TOTP 的更多信息。如果您在插件配置中将 skipVerificationOnEnable 设置为 true,则可以跳过验证。

默认情况下,双重身份验证插件要求用户拥有凭证(密码)账户。若要允许无密码用户(passkey、magic link、邮箱 OTP、OAuth/社交登录或匿名用户)启用并管理 2FA,请设置 allowPasswordless: true。此选项不会改变哪些登录方式会被 2FA 质询。

使用 2FA 登录

当启用了 2FA 的用户尝试通过邮箱、用户名或手机号登录时,响应对象将包含设置为 truetwoFactorRedirect,以及 twoFactorMethods——一个包含该用户可用 2FA 方法的数组(例如 ["totp"]["totp", "otp"])。使用 twoFactorMethods 来决定显示哪种验证界面。

默认情况下,2FA 登录强制适用于基于凭证的登录端点:/sign-in/email/sign-in/username/sign-in/phone-number。诸如邮箱 OTP、magic link、OAuth/社交登录、passkey、匿名登录以及类似的无密码流程,默认不会被 2FA 限制。

如果您的应用需要对这些登录方式也强制要求 2FA,请为这些端点添加自定义 hook 处理,并在将登录视为完成之前,把用户重定向到您的 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) {
                const methods = context.data.twoFactorMethods // 例如 ["totp", "otp"]
                // 根据可用的方法显示适当的 2FA 验证 UI
            }
        },
    }
)

使用 onTwoFactorRedirect 配置示例:

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

const authClient = createAuthClient({
    plugins: [
        twoFactorClient({
            onTwoFactorRedirect({ twoFactorMethods }){
                // twoFactorMethods 例如 ["totp", "otp"]
                // 在全局处理 2FA 验证
            },
        }),
    ],
});

使用 twoFactorPage 配置:

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

const authClient = createAuthClient({
    plugins: [
        twoFactorClient({
            twoFactorPage: "/two-factor", // 如果用户需要验证第二因素,重定向到该页面
        }),
    ],
});

使用 twoFactorPage 选项会导致在将用户重定向到双重身份验证页面时发生完整页面重载。如果要避免页面重载,请考虑改用 onTwoFactorRedirect 回调以在应用程序内以编程方式处理重定向。

使用 auth.api

当您在服务器上调用 auth.api.signInEmail,并且用户启用了 2FA 时,它将返回一个对象,其中 twoFactorRedirect 设置为 true。此行为在 TypeScript 中无法推断,可能会产生误导。您可以使用 in 来检查 twoFactorRedirect 是否设置为 true

authClient.twoFactor.* 在浏览器中自动处理 cookie。如果您在服务器上继续使用 auth.api.* 进行 2FA 流程,则必须传递相关的标头,以便 Better Auth 可以读取当前的 2FA 状态并设置相应的 2FA/session cookie。下方生成的示例使用 await headers() 获取这些标头,但在其他框架中,您应该传递等效的传入请求头。

如果在同一个服务器流程中链式调用多个 auth.api.* 调用,请确保将前一个身份验证响应中的 cookie 传递到下一个调用中。

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

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

if ("twoFactorRedirect" in response) {
	// response.twoFactorMethods 例如 ["totp", "otp"]
	// 将 responseHeaders 中的 cookie 传递到下一个 auth.api 2FA 调用。
	// 在适当的位置处理 2FA 验证
}

禁用 2FA

要禁用双重身份验证,请使用用户的密码(凭证账户必填)调用 twoFactor.disable。如果启用了 allowPasswordless,对于没有凭证账户的用户可以省略密码。

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

用户的密码(凭证账户必填)

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

用户的密码(凭证账户必填)

示例:使用 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 通过调用客户端的 authClient.twoFactor.sendOtp 函数或服务器端的 auth.api.sendTwoFactorOTP 完成。该函数会触发您在 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 代码后,您可以使用客户端的 authClient.twoFactor.verifyOtp 或服务器端的 auth.api.verifyTwoFactorOTP 进行验证。

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,});if (data) {    // 向用户展示备用代码}
Parameters
passwordstring

用户的密码(凭证账户必填)。

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

使用备用代码

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

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
-
Whether two factor authentication is enabled for the user.

表:twoFactor

Table
字段
类型
描述
id
string
PK
The ID of the two factor authentication.
userId
string
FK
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.
verified
boolean
-
Whether this TOTP secret has been verified during enrollment

配置选项

服务器端

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

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

allowPasswordless:允许对没有凭据账户的用户在没有密码的情况下启用和管理 2FA。如果存在凭据账户,则仍然需要密码。此选项不会更改哪些登录方式会在 2FA 时被质询。

Issuer:签发者是您的应用程序名称。它用于生成 TOTP 代码,并会显示在身份验证器应用中。

TOTP 选项

TOTP 的相关配置。

Prop

Type

OTP Options

OTP-related configuration.

Prop

Type

备用代码选项

当用户启用双因素认证时,备用代码会在数据库中生成并存储。如果用户无法访问其手机或电子邮件,这些代码可用于恢复对账户的访问。

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({ twoFactorMethods }){ 
                // twoFactorMethods 例如 ["totp", "otp"]
                window.location.href = "/2fa" // 处理 2FA 验证重定向
            } 
        }) 
    ]
})

选项

onTwoFactorRedirect:当用户需要验证其双重身份验证代码时调用的回调函数。接收一个包含 twoFactorMethods 的上下文对象——一个已启用的双重身份验证方法数组(例如 ["totp", "otp"])。这可用于将用户重定向到适当的 2FA 页面。