使用Fido2.AspNet实现无密码登录时遇挑战不匹配异常
解决Fido2.AspNet注册时"Authenticator response challenge does not match original challenge"异常
问题场景
使用Fido2.AspNet 4.0.0-beta.16实现无密码登录注册流程时,调用MakeNewCredentialAsync验证前端返回的凭证响应时抛出异常:
Authenticator response challenge does not match original challenge
流程步骤:
- 服务器调用
fido2.RequestNewCredential生成注册选项,存入Redis缓存后返回给Angular前端 - 前端通过
@ownid/webauthn的fido2Create方法处理选项,将返回结果的data字段传回服务器 - 服务器从Redis取出缓存选项,构造
MakeNewCredentialParams并调用MakeNewCredentialAsync时触发异常
相关代码:
服务器端验证代码
var options = await cache.GetStringAsync(...); var makeNewCredentialParams = new MakeNewCredentialParams { AttestationResponse = request.AttestationResponse, IsCredentialIdUniqueToUserCallback = ..., OriginalOptions = CredentialCreateOptions.FromJson(options) }; var credential = await fido2.MakeNewCredentialAsync(makeNewCredentialParams, cancellationToken);
Angular前端代码
async register(email: string) { const response = await lastValueFrom(this.#http.post('account/registerStart', email)) const fido = await fido2Create(response, email) return await lastValueFrom(this.#http.post('account/registerEnd', { email, attestationResponse: fido.data })) as string }
可能原因及解决方案
1. Challenge编码/解码格式不一致
FIDO2规范中Challenge通常以Base64URL格式传递,但服务器生成的CredentialCreateOptions.Challenge是byte[]类型,序列化到JSON时会转为Base64字符串。若前端传回的Challenge是Base64URL格式,直接反序列化会导致字节数组不匹配。
解决方法:
- 手动校验前后端Challenge一致性:生成选项时记录Challenge的Base64和Base64URL格式;前端传回响应后,将Challenge从Base64URL解码为
byte[],与原始Challenge对比。 - 服务器端解析前端传入的Challenge时使用Base64URL解码:
// 将前端传入的Base64URL格式Challenge转为byte[] var incomingChallenge = Base64UrlTextEncoder.Decode(request.AttestationResponse.Challenge); var originalChallenge = OriginalOptions.Challenge; // 提前校验便于排查问题 if (!incomingChallenge.SequenceEqual(originalChallenge)) { // 记录日志输出两个Challenge的Base64值,定位差异 throw new InvalidOperationException("Challenge mismatch detected"); }
2. Redis缓存的选项序列化/反序列化错误
若存入Redis的CredentialCreateOptions序列化方式不正确,会导致反序列化后的Challenge值被篡改。
解决方法:
- 序列化时使用正确的JSON配置,确保
byte[]类型的Challenge被正确序列化为Base64字符串:// 存入Redis时的序列化代码 var options = fido2.RequestNewCredential(...); var optionsJson = JsonSerializer.Serialize(options, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }); await cache.SetStringAsync(cacheKey, optionsJson); - 取出缓存后,验证
CredentialCreateOptions.FromJson(options)得到的OriginalOptions.Challenge是否与生成时一致(可打印两者的Base64值对比)。
3. 缓存Key匹配错误
若Redis缓存使用的Key与当前用户不绑定,会取出其他用户的注册选项,导致Challenge不匹配。
解决方法:
- 缓存Key与用户强绑定,比如使用
$"fido2:register:{userId}"作为Key,确保每个用户的注册选项独立存储。
4. Beta版本已知问题
使用的4.0.0-beta.16属于测试版本,可能存在未修复的Bug。
解决方法:
- 查看Fido2.AspNet的Release Notes,确认该版本是否存在Challenge校验相关的已知问题,尝试升级到最新稳定版或更高版本的Beta版。
内容的提问来源于stack exchange,提问作者Gargoyle
相关产品推荐
相关产品推荐

