mcpField
T
two-factor-authentication-best-practices

two-factor-authentication-best-practices

github.com/midudev安全7.0k61 次安装1 周前更新
代码仓库

关于 two-factor-authentication-best-practices

面向使用 Better Auth 实现登录安全的开发者的双重身份验证指南,涵盖 TOTP、邮件或短信 OTP、备用码、受信任设备及相关配置与防护措施。

主要功能

  1. 01TOTP 设置与验证
  2. 02邮件和短信 OTP 流程
  3. 03一次性备用码
  4. 04受信任设备配置
  5. 05速率限制与 OTP 尝试次数
  6. 06密钥存储与会话处理

适用场景

  1. 01为 Better Auth 添加双重验证
  2. 02实现验证器应用登录
  3. 03提供账户恢复码
  4. 04配置 OTP 安全控制

安装

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

$npx skills add https://github.com/midudev/autoskills --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