← 中文资源

简体中文版本

添加带恢复码的 TOTP MFA 注册

生成配对二维码、首个验证码通过后才启用、密钥加密存储,并签发一次性恢复码。

阅读英文原文

目标

用户一扫二维码就翻开“已启用 MFA”开关,会在扫码静默失败时制造锁死、在明文存密钥时制造失陷入口。健全的注册是一份契约:用户先证明因素可用才激活、密钥加密存储、并在用户注意力最集中的时刻签发十枚一次性恢复码。这样的设计才能活过“手机掉进湖里”的那一天。

步骤要点

  1. 生成 20 字节密钥并用 keyuri 构造 otpauth:// 配对 URI,渲染成注册二维码。
  2. 用户输入当前验证码,verify 通过后才 enableTotp 并加密落库,杜绝“扫坏也启用”。
  3. 注册当场签发 10 枚一次性恢复码,服务端只存 SHA-256 哈希,逐枚核销。
  4. 验证端点按用户限流每分钟 5 次,让六位码穷举在数学上无望。
  5. 端到端演练锁死路径:生成当前码登录、用恢复码登录并确认其被核销、引导重新注册新设备。

排错提示

注册后验证码总被拒多半是服务器时钟漂移,允许一步窗口并修好 NTP;手机丢了恢复码是正路,恢复码也丢则只能走人工身份核验重注册,慢本身就是应有的安全属性;二维码扫不出来别手拼 URI,用库的 keyuri 并带上 issuer 参数。

适用边界

TOTP 密钥可被实时钓鱼,强威胁场景应考虑抗钓鱼的 WebAuthn;恢复码的敏感度等同密码,只能一次性交付、只存哈希。

代码示例

// 1. Secret + provisioning URI
const secret = authenticator.generateSecret(20);
const uri = authenticator.keyuri(user.email, "ExampleCorp", secret);

// 2. Activate only after a verified live code
if (!authenticator.verify({ token: req.body.token, secret })) {
  return res.status(400).json({ error: "code did not match" });
}
await users.enableTotp(userId, encrypt(secret));