mcpField
T
two-factor-authentication-best-practices

two-factor-authentication-best-practices

github.com/better-auth安全22734.5k 次安装8 周前更新
代码仓库

关于 two-factor-authentication-best-practices

面向使用 Better Auth 的开发者,介绍为账号加入双重验证的配置与实施。指南涵盖 TOTP、邮件或短信 OTP、备用码及可信设备,并说明相关安全设置。

主要功能

  1. 01TOTP 验证器配置
  2. 02邮件或短信 OTP
  3. 03备用码生成与恢复
  4. 04可信设备管理
  5. 05验证码存储与速率限制

适用场景

  1. 01为密码账号添加第二验证步骤
  2. 02通过备用码恢复账号访问
  3. 03配置 OTP 发送和存储方式
  4. 04管理可信设备的登录体验

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/better-auth/skills --skill two-factor-authentication-best-practices

安装后在 Claude Code 中输入/two-factor-authentication-best-practices确认已加载。

详细介绍

设置

  1. 在服务器配置中添加 twoFactor() 插件,并设置 issuer
  2. 在客户端配置中添加 twoFactorClient() 插件
  3. 运行 npx auth@latest migrate(内置适配器),或为 Drizzle/Prisma 生成并推送迁移
  4. 验证:检查用户表中是否存在 twoFactorSecret 列
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  appName: "My App",
  plugins: [
    twoFactor({
      issuer: "My App",
    }),
  ],
});
客户端设置
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      onTwoFactorRedirect() {
        window.location.href = "/2fa";
      },
    }),
  ],
});

为用户启用 2FA

需要验证密码。返回 TOTP URI(用于生成二维码)和备用码。

const enable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.enable({
    password,
  });

  if (data) {
    // data.totpURI — generate a QR code from this
    // data.backupCodes — display to user
  }
};

在首次 TOTP 验证成功之前,twoFactorEnabled 不会设为 true。可通过 skipVerificationOnEnable: true 覆盖此行为(不推荐)。

TOTP(身份验证器应用)

显示二维码
import QRCode from "react-qr-code";

const TotpSetup = ({ totpURI }: { totpURI: string }) => {
  return <QRCode value={totpURI} />;
};
验证 TOTP 代码

接受当前时间前后一个周期内的代码:

const verifyTotp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyTotp({
    code,
    trustDevice: true,
  });
};
TOTP 配置选项
twoFactor({
  totpOptions: {
    digits: 6, // 6 or 8 digits (default: 6)
    period: 30, // Code validity period in seconds (default: 30)
  },
});

OTP(电子邮件/短信)

配置 OTP 发送方式
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    twoFactor({
      otpOptions: {
        sendOTP: async ({ user, otp }, ctx) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5, // Code validity in minutes (default: 3)
        digits: 6, // Number of digits (default: 6)
        allowedAttempts: 5, // Max verification attempts (default: 5)
      },
    }),
  ],
});
发送和验证 OTP

发送:authClient.twoFactor.sendOtp()。验证:authClient.twoFactor.verifyOtp({ code, trustDevice: true })。

OTP 存储安全

配置 OTP 代码在数据库中的存储方式:

twoFactor({
  otpOptions: {
    storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
  },
});

自定义加密方式:

twoFactor({
  otpOptions: {
    storeOTP: {
      encrypt: async (token) => myEncrypt(token),
      decrypt: async (token) => myDecrypt(token),
    },
  },
});

备用码

启用 2FA 时自动生成。每个代码只能使用一次。

显示备用码
const BackupCodes = ({ codes }: { codes: string[] }) => {
  return (
    <div>
      <p>Save these codes in a secure location:</p>
      <ul>
        {codes.map((code, i) => (
          <li key={i}>{code}</li>
        ))}
      </ul>
    </div>
  );
};
重新生成备用码

使所有先前的代码失效:

const regenerateBackupCodes = async (password: string) => {
  const { data, error } = await authClient.twoFactor.generateBackupCodes({
    password,
  });
  // data.backupCodes contains the new codes
};
使用备用码恢复访问
const verifyBackupCode = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyBackupCode({
    code,
    trustDevice: true,
  });
};
备用码配置
twoFactor({
  backupCodeOptions: {
    amount: 10, // Number of codes to generate (default: 10)
    length: 10, // Length of each code (default: 10)
    storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
  },
});

处理登录时的 2FA

需要 2FA 时,响应中会包含 twoFactorRedirect: true:

登录流程
  1. 调用 signIn.email({ email, password })
  2. 在 onSuccess 中检查 context.data.twoFactorRedirect
  3. 如果为 true,则跳转到 /2fa 验证页面
  4. 使用 TOTP、OTP 或备用码进行验证
  5. 验证成功后创建会话 Cookie
const signIn = async (email: string, password: string) => {
  const { data, error } = await authClient.signIn.email(
    { email, password },
    {
      onSuccess(context) {
        if (context.data.twoFactorRedirect) {
          window.location.href = "/2fa";
        }
      },
    }
  );
};

服务器端:使用 auth.api.signInEmail 时,检查响应中是否包含 "twoFactorRedirect"。

受信任设备

验证时传入 trustDevice: true。默认信任时长为 30 天(trustDeviceMaxAge)。每次登录时都会刷新。

安全注意事项

会话管理

流程:凭据 → 移除会话 → 临时 2FA Cookie(默认 10 分钟)→ 验证 → 创建会话。

twoFactor({
  twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});
速率限制

内置限制:所有 2FA 端点每 10 秒最多请求 3 次。OTP 还有额外的尝试次数限制:

twoFactor({
  otpOptions: {
    allowedAttempts: 5, // Max attempts per OTP code (default: 5)
  },
});
静态数据加密

TOTP 密钥:使用 auth secret 加密。备用码:默认加密。OTP:可配置("plain"、"encrypted"、"hashed")。验证时使用恒定时间比较。

只有凭据(电子邮件/密码)账户才能启用 2FA。

禁用 2FA

需要确认密码。会撤销受信任设备记录:

const disable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.disable({
    password,
  });
};

完整配置示例

import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  appName: "My App",
  plugins: [
    twoFactor({
      // TOTP settings
      issuer: "My App",
      totpOptions: {
        digits: 6,
        period: 30,
      },
      // OTP settings
      otpOptions: {
        sendOTP: async ({ user, otp }) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5,
        allowedAttempts: 5,
        storeOTP: "encrypted",
      },
      // Backup code settings
      backupCodeOptions: {
        amount: 10,
        length: 10,
        storeBackupCodes: "encrypted",
      },
      // Session settings
      twoFactorCookieMaxAge: 600, // 10 minutes
      trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days
    }),
  ],
});

评论

支持 Markdown