如何在WebAuthn登录流程中正确使用counter计数器
WebAuthn Passkey 登录流程中 Counter 值不递增的问题解决
核心问题定位
你在登录流程里犯了一个关键错误:误用了注册阶段的 attestationOptions() 来生成登录请求选项,这直接导致认证器没有触发 counter 递增逻辑。登录阶段必须使用断言(Assertion)相关的方法,而非注册(Attestation)方法。
正确流程与代码修正
1. 生成登录请求选项(替换错误的 attestationOptions)
登录时要调用 assertionOptions() 生成断言请求,而非注册阶段的 attestationOptions(),示例代码:
// 登录流程:生成断言请求选项 const assertionOptions = await f2l.assertionOptions({ allowCredentials: [ { type: "public-key", id: passkey.credId, // 传入你存储的凭证ID(需转为Uint8Array) transports: passkey.transports // 可选,传入存储的凭证传输方式 } ], userVerification: "required" // 和注册时的配置保持一致或按需调整 }); // 将生成的challenge存储到服务端,后续验证断言时要用 storedChallenge = assertionOptions.challenge;
2. 正确获取验证后的 Counter 值
Counter 不在 clientData 中,而是存储在认证器返回的 authnrData 里,修正获取方式:
const assertionExpectations = { challenge: storedChallenge, origin: 'http://localhost:4200', factor: 'either', publicKey: passkey.publicKey, // 存储的公钥 prevCounter: passkey.counter, // 存储的上一次counter值 userHandle: new TextEncoder().encode(user.email), // 存储的用户ID }; const authResult = await this.f2l.assertionResult(assertionResponse, assertionExpectations); // 从authnrData中获取递增后的counter const newCounter = authResult.authnrData.get('counter'); console.log(newCounter); // 现在会输出递增后的值,比如1、2等
3. Counter 机制的正确运作逻辑
- 注册阶段:首次注册成功后,认证器会返回初始 counter(通常为0),你需要把这个值和公钥、凭证ID等一起持久化存储到服务端。
- 登录阶段:
- 服务端生成断言选项时不需要传入 counter,counter 是认证器内部维护的状态,由认证器负责递增。
- 验证断言时,必须传入
prevCounter(即之前存储的旧counter值),Fido2Lib 会自动检查返回的新counter是否大于旧值,以此防止重放攻击。 - 验证成功后,务必将服务端存储的counter更新为新返回的值,供下次登录验证使用。
内容的提问来源于stack exchange,提问作者Jeanluca Scaljeri
相关产品推荐
相关产品推荐

