☰
Firebase JS SDK 的 TotpMultiFactorAssertion:TOTP 第二因子认证断言的注册与登录实战解析
2026/10/15 20:51:46 网站建设 项目流程
  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载

本文以 Firebase JavaScript SDK 官方 API 参考文档 auth.totpmultifactorassertion.md 为骨架,深入讲解TotpMultiFactorAssertion接口在 TOTP(基于时间的一次性密码)多因子认证体系中的定位、产生方式与底层实现。读完本文,你将掌握:如何用TotpMultiFactorGenerator构造 TOTP 断言完成第二因子的注册与二次登录、断言的内部状态机如何驱动注册/登录两个 API 流程,以及 SDK 中对应的源码与测试证据位置。

一、TotpMultiFactorAssertion 是什么

按照官方参考文档的定义,TotpMultiFactorAssertion是:

The class for asserting ownership of a TOTP second factor.

即用于证明用户拥有某个 TOTP 第二因子的断言对象。它本身不包含任何业务字段,而是通过继承关系接入 Firebase Auth 的多因子断言体系:

export interface TotpMultiFactorAssertion extends MultiFactorAssertion {}

其基类 MultiFactorAssertion 定义在 packages/auth/src/model/public_types.ts#L611,只有一个只读属性factorId,用于标识第二因子的类型。TOTP 断言在构造时即被固定为FactorId.TOTP,即字符串字面量'totp'。

在 Firebase Auth 的 MFA 模型里,一个"断言(assertion)"是用户在完成第二因子验证(输入验证码、提供 TOTP 一次性密码等)后产生的一个凭证载体,它不直接携带敏感信息,而是由 SDK 内部把断言携带的 OTP 与注册/登录上下文(session)组合成对后端的请求。这一点可以从 mfa_assertion.ts 的抽象基类看到:所有断言实现都实现了_finalizeEnroll与_finalizeSignIn两个内部方法,分别服务于"注册第二因子"和"用第二因子登录"两条路径。

二、谁创建它:TotpMultiFactorGenerator 工厂

官方文档明确指出,TotpMultiFactorAssertion只能由 TotpMultiFactorGenerator 提供,具体来自它的两个静态工厂方法:

方法签名用途
assertionForEnrollmentassertionForEnrollment(secret: TotpSecret, oneTimePassword: string): TotpMultiFactorAssertion完成 TOTP 第二因子的注册(配合 MultiFactorUser.enroll 使用)
assertionForSignInassertionForSignIn(enrollmentId: string, oneTimePassword: string): TotpMultiFactorAssertion完成 TOTP 第二因子的登录(配合 MultiFactorResolver.resolveSignIn 使用)

两者的差异集中在构造参数上:

  • 注册场景传入TotpSecret——注册阶段服务端下发的共享密钥对象;
  • 登录场景传入enrollmentId——用户在服务端已注册的 TOTP 因子的标识(通常来自resolver.hints中选中的MultiFactorInfo.uid)。

从源码 totp.ts#L54-L78 可以看到,这两个方法只是对内部实现的简单封装:

static assertionForEnrollment( secret: TotpSecret, oneTimePassword: string ): TotpMultiFactorAssertion { return TotpMultiFactorAssertionImpl._fromSecret(secret, oneTimePassword); } static assertionForSignIn( enrollmentId: string, oneTimePassword: string ): TotpMultiFactorAssertion { return TotpMultiFactorAssertionImpl._fromEnrollmentId( enrollmentId, oneTimePassword ); }

此外,TotpMultiFactorGenerator还提供了FACTOR_ID: 'totp'静态常量(FactorId.TOTP),以及用于开启注册流程的generateSecret(session: MultiFactorSession): Promise<TotpSecret>,后面会展开。

三、源码视角:断言内部实现 TotpMultiFactorAssertionImpl

虽然TotpMultiFactorAssertion接口本身是空壳,但它的真正实现类是TotpMultiFactorAssertionImpl,位于 packages/auth/src/mfa/assertions/totp.ts#L113-L176。它的构造函数同时容纳两种场景所需的信息:

export class TotpMultiFactorAssertionImpl extends MultiFactorAssertionImpl implements TotpMultiFactorAssertion { constructor( readonly otp: string, readonly enrollmentId?: string, readonly secret?: TotpSecret ) { super(FactorId.TOTP); } }

即断言内部持有:用户输入的 OTP、登录场景的enrollmentId、注册场景的secret。两个工厂方法分别构造不同的组合:

  • _fromSecret(secret, otp)→new TotpMultiFactorAssertionImpl(otp, undefined, secret)(注册态)
  • _fromEnrollmentId(enrollmentId, otp)→new TotpMultiFactorAssertionImpl(otp, enrollmentId)(登录态)

当用户把断言交给multiFactor(user).enroll(assertion)或resolver.resolveSignIn(assertion)时,SDK 会调用基类 MultiFactorAssertionImpl._process,根据当前MultiFactorSession的类型(ENROLL还是SIGN_IN)自动分发到_finalizeEnroll或_finalizeSignIn:

  • _finalizeEnroll会先断言secret必须存在(否则抛auth/argument-error),然后用secret._makeTotpVerificationInfo(otp)生成{ sessionInfo, verificationCode },调用finalizeEnrollTotpMfa(totp.ts#L142-L157);
  • _finalizeSignIn会断言enrollmentId与otp都必须存在,然后以{ mfaPendingCredential, mfaEnrollmentId, totpVerificationInfo: { verificationCode } }调用finalizeSignInTotpMfa(totp.ts#L160-L175)。

这正是"断言"一词的含义:它把用户的一次性密码与上下文绑定,在合适的时间点消费掉。

四、配合 TotpSecret:注册断言的密钥来源

注册场景的断言依赖TotpSecret(见 auth.totpsecret.md),它由TotpMultiFactorGenerator.generateSecret(session)产生,封装了服务端返回的 TOTP 参数:

属性含义
secretKey共享密钥/种子,用于生成 OTP 与二维码
hashingAlgorithm使用的哈希算法(如SHA1)
codeLength一次性密码位数(如 6)
codeIntervalSecondsOTP 刷新周期(秒,通常 30)
enrollmentCompletionDeadline注册完成的 UTC 截止时间字符串,可用于倒计时提示

generateSecret的源码实现(totp.ts#L89-L105)会先断言 session 中携带用户(否则抛auth/internal-error),然后向后端START_MFA_ENROLLMENT端点发送{ idToken, totpEnrollmentInfo: {} },并把响应的totpSessionInfo解析为TotpSecret。

TotpSecret还提供generateQrCodeUrl(accountName?, issuer?)方法(totp.ts#L256-L270),生成符合 Key-Uri-Format 的otpauth://totp/...链接,可直接渲染成二维码供 Google Authenticator 等 TOTP App 扫描。若未传参数,SDK 默认使用当前用户邮箱作为accountName(无用户时为unknownuser)、Firebase App 名称作为issuer。该默认行为在单元测试 totp.test.ts#L305-L343 中有完整覆盖。

五、实战流程一:注册 TOTP 第二因子

完整注册流程需要四步:获取 multiFactorSession → 生成 TotpSecret 与二维码 → 用户扫码并输入动态码 → 构造断言并 enroll。仓库自带的 demo 应用 packages/auth/demo/src/index.js#L890-L952 给出了可直接参考的写法:

// 1. 为当前已登录用户创建 MFA 会话 const multiFactorSession = await multiFactor(activeUser()).getSession(); // 2. 向服务端申请 TOTP 密钥,并生成二维码链接 const totpSecret = await TotpMultiFactorGenerator.generateSecret(multiFactorSession); const url = totpSecret.generateQrCodeUrl('test', 'testissuer'); console.log('TOTP URL is ' + url); console.log('Finalize sign in by ' + totpSecret.enrollmentCompletionDeadline); // 3. 用户用 TOTP App 扫码,输入动态验证码后: const verificationCode = $('#enroll-mfa-totp-verification-code').val(); const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment( totpSecret, verificationCode ); // 4. 完成注册(displayName 可选,用于标识该因子) await multiFactor(activeUser()).enroll(multiFactorAssertion, displayName);

注意 demo 中还利用enrollmentCompletionDeadline做了每秒倒计时展示,超时后提示 "TOTP enrollment expired!",这正是该字段的典型用途(index.js#L903-L921)。

六、实战流程二:用 TOTP 第二因子完成登录

当用户已注册 TOTP 但尚未通过第二因子验证时,首次登录(如signInWithEmailAndPassword)会抛出auth/multi-factor-auth-required错误。此时应通过getMultiFactorResolver获取resolver,用resolver.hints让用户选择要使用的 TOTP 因子,再构造登录断言:

import { getMultiFactorResolver, TotpMultiFactorGenerator } from 'firebase/auth'; signInWithEmailAndPassword(auth, email, password).catch((error) => { if (error.code === 'auth/multi-factor-auth-required') { const resolver = getMultiFactorResolver(auth, error); // resolver.hints 中包含已注册因子,找到 TOTP 因子后取其 uid const selectedMultiFactorHint = resolver.hints[0]; // 用户输入 TOTP App 中的动态码后: const otp = $('#multi-factor-totp-sign-in-verification-code').val(); const assertion = TotpMultiFactorGenerator.assertionForSignIn( selectedMultiFactorHint.uid, // 即 enrollmentId otp ); resolver.resolveSignIn(assertion).then((userCredential) => { // 登录成功,userCredential.user 为已通过双因子验证的用户 }); } });

该流程与 demo 中的 onFinalizeSignInWithTotpMultiFactor 完全一致,其底层行为在集成测试 packages/auth/test/integration/flows/totp.test.ts 中有端到端验证。

七、底层 API 调用链与请求结构

断言最终都要落到 Firebase Auth REST API 上,相关定义位于 packages/auth/src/api/account_management/mfa.ts 与 packages/auth/src/api/authentication/mfa.ts:

阶段端点关键请求字段
生成密钥START_MFA_ENROLLMENT(startEnrollTotpMfa)idToken、totpEnrollmentInfo
完成注册FINALIZE_MFA_ENROLLMENT(finalizeEnrollTotpMfa)idToken、displayName?、totpVerificationInfo: { sessionInfo, verificationCode }
完成登录FINALIZE_MFA_SIGN_IN(finalizeSignInTotpMfa)mfaPendingCredential、mfaEnrollmentId、totpVerificationInfo: { verificationCode }

其中StartTotpMfaEnrollmentResponse的totpSessionInfo结构(mfa.ts#L120-L129)包含sharedSecretKey、verificationCodeLength、hashingAlgorithm、periodSec、sessionInfo、finalizeEnrollmentTime六个字段,正是第四节TotpSecret各属性的直接来源。

单元测试 totp.test.ts 精确断言了这些请求体。例如注册断言被_process消费后,发出的请求体必须严格等于:

{ idToken: 'enrollment-id-token', totpVerificationInfo: { verificationCode: '123456', sessionInfo: 'verification-id' } }

而登录断言发出的请求体为:

{ mfaPendingCredential: 'mfa-pending-credential', mfaEnrollmentId: 'enrollment-id', totpVerificationInfo: { verificationCode: '123456' } }

八、错误处理与边界情况

从实现与测试中可以确认以下边界行为:

  • 注册态缺少 secret:_finalizeEnroll对secret做_assert,缺失时抛auth/argument-error;
  • 登录态缺少 enrollmentId 或 otp:_finalizeSignIn同样抛auth/argument-error,对应测试见 totp.test.ts#L254-L274;
  • session 缺少用户:generateSecret抛auth/internal-error(totp.test.ts#L87-L97);
  • OTP 过期:注册有时间窗口,enrollmentCompletionDeadline可用于前端倒计时并在过期后终止流程;
  • 错误的动态码:注册/登录请求会被后端拒绝并返回相应 Firebase Auth 错误码,断言本身不缓存任何状态,可安全地根据新 OTP 重新构造。

九、总结

TotpMultiFactorAssertion虽是一个空接口,却是 Firebase JS SDK TOTP MFA 两条核心流程(注册与二次登录)的统一入口:它由TotpMultiFactorGenerator的两个静态工厂方法构造,内部由TotpMultiFactorAssertionImpl依据会话类型分别驱动FINALIZE_MFA_ENROLLMENT与FINALIZE_MFA_SIGN_IN两个后端端点。理解这个断言对象的产生方式与消费链路,是正确接入 Firebase Auth TOTP 多因子认证的关键。相关类型定义集中在 packages/auth/src/model/public_types.ts#L1273,完整实现与测试可分别查看 packages/auth/src/mfa/assertions/totp.ts 与 packages/auth/src/mfa/assertions/totp.test.ts。

  • 开发工具
  • 后端

【免费下载链接】firebase-js-sdk

Firebase Javascript SDK

项目地址:https://gitcode.com/gh_mirrors/fi/firebase-js-sdk
点击查看免费下载

相关推荐

上一篇:GreaterWMS开源仓库管理系统:企业级仓储数字化解决方案
下一篇:DayZ终极离线生存指南:为什么你需要这个完整的单机体验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询