双因素认证 (2FA)

OTP TOTP Backup Codes Trusted Devices

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

此插件提供两种主要方法来进行第二因素验证:

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

附加功能包括:

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

安装

将插件添加到您的认证配置

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

auth.ts
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() 
    ]
})

迁移数据库

运行迁移或生成架构,以向数据库添加必要的字段和表。

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",});
PropDescriptionType
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 时:

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

注意:twoFactorEnabled 不会设置为 true,直到用户验证他们的 TOTP 代码。请参阅 here 了解更多关于验证 TOTP 的信息。您可以通过在插件配置中将 skipVerificationOnEnable 设置为 true 来跳过验证。

目前,双因素只能为凭证账户启用。对于社交账户,假设提供商已经处理了 2FA。

使用 2FA 登录

当启用了 2FA 的用户尝试通过电子邮件登录时,响应对象将包含 twoFactorRedirect 设置为 true。这表示用户需要验证他们的 2FA 代码。

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

sign-in.tsx
await authClient.signIn.email({
        email: "user@example.com",
        password: "password123",
    },
    {
        async onSuccess(context) {
            if (context.data.twoFactorRedirect) {
                // Handle the 2FA verification in place
            }
        },
    }
)

使用 onTwoFactorRedirect 配置:

sign-in.ts
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,并且用户启用了 2FA 时,它将返回一个对象,其中 twoFactorRedirect 设置为 true。这种行为在 TypeScript 中无法推断,这可能会误导。您可以使用 in 来检查 twoFactorRedirect 是否设置为 true

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

if ("twoFactorRedirect" in response) {
	// Handle the 2FA verification in place
}

禁用 2FA

要禁用双因素认证,请使用用户的密码调用 twoFactor.disable

POST
/two-factor/disable
const { data, error } = await authClient.twoFactor.disable({    password, // required});
PropDescriptionType
password
The user's password
string

TOTP

TOTP(基于时间的临时密码)是一种算法,它使用时间作为计数器为每个登录尝试生成一个唯一的密码。每固定间隔(Better Auth 默认 30 秒),生成一个新密码。这解决了传统密码的几个问题:它们可能被忘记、被窃取或被猜测。OTP 解决了其中一些问题,但通过 SMS 或电子邮件交付它们可能不可靠(甚至有风险,因为它打开了新的攻击向量)。

然而,TOTP 离线生成代码,使其既安全又方便。您只需在手机上安装一个认证器应用。

获取 TOTP URI

启用 2FA 后,您可以获取 TOTP URI 以显示给用户。此 URI 由服务器使用 secretissuer 生成,可用于生成 QR 码,用户可以用他们的认证器应用扫描。

POST
/two-factor/get-totp-uri
const { data, error } = await authClient.twoFactor.getTotpUri({    password, // required});
PropDescriptionType
password
The user's password
string

示例:使用 React

一旦您获取了 TOTP URI,您就可以使用它为用户生成 QR 码,以便他们用认证器应用扫描。

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 的发行者设置为认证配置中提供的应用名称,如果未提供,则设置为 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,});
PropDescriptionType
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 验证第二因素之前,您需要在 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) {
                    // send otp to user
				},
			},
        })
    ]
})

发送 OTP

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

POST
/two-factor/send-otp
const { data, error } = await authClient.twoFactor.sendOtp({    trustDevice: true,});if (data) {    // redirect or show the user to enter the code}
PropDescriptionType
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 代码后,您可以验证它

POST
/two-factor/verify-otp
const { data, error } = await authClient.twoFactor.verifyOtp({    code: "012345", // required    trustDevice: true,});
PropDescriptionType
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

备份代码

备份代码在数据库中生成并存储。如果用户丢失对手机或电子邮件的访问权限,可以使用此代码恢复对账户的访问。

生成备份代码

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

POST
/two-factor/generate-backup-codes
const { data, error } = await authClient.twoFactor.generateBackupCodes({    password, // required});if (data) {    // Show the backup codes to the user}
PropDescriptionType
password
The users password.
string

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

使用备份代码

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

POST
/two-factor/verify-backup-code
const { data, error } = await authClient.twoFactor.verifyBackupCode({    code: "123456", // required    disableSession: false,    trustDevice: true,});
PropDescriptionType
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

一旦使用备份代码,它将从数据库中移除且无法再次使用。

查看备份代码

要向用户显示备份代码,您可以在服务器上调用 viewBackupCodes。这将返回响应中的备份代码。您仅应在用户具有新鲜会话时使用此功能——刚刚创建的会话。

GET
/two-factor/view-backup-codes
const data = await auth.api.viewBackupCodes({    body: {        userId: "user-id",    },});
PropDescriptionType
userId?
The user ID to view all backup codes.
string | null

受信任设备

通过向 verifyTotpverifyOtp 传递 trustDevice 来将设备标记为受信任。

const verify2FA = async (code: string) => {
    const { data, error } = await authClient.twoFactor.verifyTotp({
        code,
        callbackURL: "/dashboard",
        trustDevice: true // Mark this device as trusted
    })
    if (data) {
        // 2FA verified and device trusted
    }
}

trustDevice 设置为 true 时,当前设备将被记住 60 天。在此期间,用户从此设备后续登录时不会被提示输入 2FA。信任期在用户成功登录时每次都会刷新。

发行者

通过添加 issuer,您可以为 2FA 应用设置您的应用名称。

例如,如果您的用户使用 Google Auth,默认 appName 将显示为 Better Auth。但是,通过使用以下代码,它将显示为 my-app-name

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

架构

插件需要在 user 表中添加 1 个额外字段,并在数据库中添加 1 个额外表来存储双因素认证数据。

表:user

Field NameTypeKeyDescription
twoFactorEnabledbooleanWhether two factor authentication is enabled for the user.

表:twoFactor

Field NameTypeKeyDescription
idstringThe ID of the two factor authentication.
userIdstringThe ID of the user
secretstringThe secret used to generate the TOTP code.
backupCodesstringThe backup codes used to recover access to the account if the user loses access to their phone or email.

选项

服务器

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

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

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" // Handle the 2FA verification redirect
            } 
        }) 
    ]
})

选项

onTwoFactorRedirect:当用户需要验证他们的 2FA 代码时将调用的回调。这可用于将用户重定向到 2FA 页面。