- 开发工具
- 后端
【免费下载链接】firebase-js-sdk
Firebase Javascript 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 提供,具体来自它的两个静态工厂方法:
| 方法 | 签名 | 用途 |
|---|---|---|
assertionForEnrollment | assertionForEnrollment(secret: TotpSecret, oneTimePassword: string): TotpMultiFactorAssertion | 完成 TOTP 第二因子的注册(配合 MultiFactorUser.enroll 使用) |
assertionForSignIn | assertionForSignIn(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) |
codeIntervalSeconds | OTP 刷新周期(秒,通常 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
相关推荐
Firebase JS SDK 手机号第二因素登录:PhoneSingleFactorInfoOptions 与 PhoneMultiFactorSignInInfoOptions 实战指南
Firebase JS SDK 手机号第二因素登录:PhoneSingleFactorInfoOptions 与 PhoneMultiFactorSignInI
开发工具后端Firebase JS SDK 多因素认证核心接口 MultiFactorAssertion 深度解析:factorId 属性、实现原理与注册/登录实战
Firebase JS SDK 多因素认证核心接口 MultiFactorAssertion 深度解析:factorId 属性、实现原理与注册/登录实战 本文围
开发工具后端Firebase Auth(firebase-js-sdk)PhoneMultiFactorSignInInfoOptions 详解:手机号第二因素登录参数配置与实现原理
Firebase Auth(firebase js sdk)PhoneMultiFactorSignInInfoOptions 详解:手机号第二因素登录参数配置
开发工具后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考