You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Firebase Blocking Functions实现自定义双因素认证(2FA)

自定义Firebase 2FA方案的安全问询

Firebase身份平台默认仅支持短信类双因素认证(2FA),对其他2FA方式的支持有限,因此我们实现了一套自定义2FA方案:

实现流程

  1. 通过Blocking Functions处理邮箱密码登录,在beforeSignIn函数中判断用户是否需要认证器类型的2FA:
const beforeSignIn = functions
  .auth.user()
  .beforeSignIn(async (user, context) => {
    const userAuth = await admin.firestore().collection("user").doc(user.uid).get()
    if (userAuth.data().multifactorType === 'authenticator') {
      console.log(`User [${user.uid}] requires 2FA with authenticator`);
    
      const loginAttempt = {eventId: context.eventId, timestamp: Timestamp.now()}
      await admin.firestore().collection('user').doc(user.uid).update({multifactorLoginAttempt: loginAttempt})
      const hmac = crypto.createHmac('sha256', 'top-secret-key').update(user.uid).update(context.eventId).digest('hex');
      throw new functions.auth.HttpsError('failed-precondition', JSON.stringify({ uid: user.uid, hmac }));      
   }
    console.log(`No 2FA in db. User will login`);
});
  1. 用户收到包含HMAC和UID的错误信息后,被重定向到OTP输入页面
  2. 前端携带UID、HMAC和OTP调用后端/api/verify-multifactor接口,验证通过后生成自定义令牌让用户登录:
multifactorRoutes.post(
  '/verify',
  async (
    req: Request<{}, {}, MultifactorVerifyType>,
    res: Response<{ token: string } | { message: string }>
  ) => {
    const userId = req.body.uid;

    const user = await auth.firestore().collection("user").doc(userId).get();
    if (!user.multifactorLoginAttempt) {
      res.status(401).send({ message: 'No existing login attempt' });
      return;
    }
    
    const hmacRecreated = crypto.createHmac('sha256', 'top-secret-key').update(userId).update(user.multifactorLoginEvent.eventId).digest('hex');
    const isExpectedHmac = crypto.timingSafeEqual(
      Buffer.from(hmacRecreated),
      Buffer.from(req.body.hmac)
    );

    if (!isExpectedHmac) {
      res.status(401).send({ message: 'Failed to verify hmac' });
      return;
    }
    
    /** using otplib **/
    const isValidOtp = authenticator.verify({
      token: req.body.token,
      secret: user.secret,
    });
    
    if (isValidOtp) {
      const token = await admin.auth().createCustomToken(userId);
      await admin.firestore().collection('user').doc(userId).update({ multifactorLoginAttempt: deleteValue() });
      res.status(200).send({ token });
    } else {
      res.status(401).send({ message: 'Illegal token' });
    }
  }
);

技术疑问

  1. 我们的/api/verify-multifactor端点是否还需要额外安全措施?目前认为HMAC+OTP能防暴力破解,但想确认是否有遗漏。
  2. 有没有其他方式从Firebase Blocking Functions获取临时令牌,替代自制的HMAC?

安全建议与替代方案

一、/api/verify-multifactor端点的额外安全措施

现有方案已具备基础安全性,可补充以下措施强化防护:

  • 限制登录尝试有效期:给multifactorLoginAttempt添加过期时间(比如15分钟),验证时先检查时间戳是否过期,避免攻击者利用旧HMAC长时间尝试破解OTP。
  • OTP错误次数限制:记录用户连续输错OTP的次数,达到阈值(比如5次)就锁定当前登录尝试,甚至临时冻结用户2FA验证权限一段时间,防止暴力破解OTP。
  • 强制HTTPS传输:确保所有前端到/verify接口的请求都通过HTTPS传输,避免HMAC、OTP等敏感数据被中间人截获。
  • 清理冗余记录:定期清理Firestore中已过期或已完成的multifactorLoginAttempt,减少攻击者可利用的旧数据。
  • 限制并发登录尝试:在beforeSignIn中更新记录时,检查用户是否已有未过期的登录尝试,存在则覆盖或拒绝新请求,避免同一用户同时存在多个待验证会话。

二、替代自制HMAC的临时令牌方案

可利用Firebase内置能力或规范工具生成临时令牌,替代自制HMAC:

  • 使用Firebase会话Cookie:在beforeSignIn中不抛出错误,而是调用createSessionCookie生成短期会话Cookie返回给前端。前端携带Cookie和OTP调用验证接口,后端先验证Cookie有效性,再校验OTP,无需自行实现HMAC逻辑。
  • 生成签名JWT:用JWT库生成包含uid、eventId和过期时间的签名JWT,密钥托管在Google Secret Manager中。后端验证JWT的签名和有效期,替代HMAC验证,JWT自带的过期和签名机制比自制HMAC更规范。
  • 使用Firebase动态链接:生成包含uid和eventId的加密动态链接,用户通过链接进入OTP输入页面,前端从链接提取参数后调用验证接口,动态链接的加密由Firebase处理,避免敏感参数暴露。

内容的提问来源于stack exchange,提问作者KimHafr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 02:12:09