WebAuthn API返回挑战令牌与发送值不符,求排查指引
WebAuthn Challenge 不一致问题的解决办法
你的问题出在前端对challenge的解码方式错误,后端生成的是Base64编码的随机字节序列,但前端没有正确解码这个Base64字符串,反而直接把字符串的每个字符转成了ASCII字节,导致WebAuthn处理的challenge和服务器生成的原始值完全不同。
问题根源
后端的GetNonce()方法是正确的:生成64字节的随机数据,然后Base64编码后返回。但前端这段代码:
options.publicKey.challenge = Uint8Array.from(options.publicKey.challenge, c => c.charCodeAt(0));
是把Base64字符串(比如asdf)的每个字符('a'、's'、'd'、'f')直接转成对应的ASCII码字节,而不是解码Base64得到原始的随机字节。WebAuthn完成流程后,会把它实际收到的字节序列重新编码成Base64返回,所以你看到的是和输入完全不同的字符串(比如YXNkZg其实是asdf的Base64去掉了末尾的填充符==)。
正确的前端处理方式
你需要先把后端返回的Base64字符串解码成原始字节数组,再传给WebAuthn API。可以用这段代码:
// 解码Base64字符串为Uint8Array function decodeBase64ToUint8Array(base64) { // 处理可能的URL安全Base64(如果后端用的是这种) base64 = base64.replace(/-/g, '+').replace(/_/g, '/'); // 补全Base64填充符 while (base64.length % 4 !== 0) { base64 += '='; } const binaryString = atob(base64); return Uint8Array.from(binaryString, c => c.charCodeAt(0)); } // 替换原来的转换代码 options.publicKey.challenge = decodeBase64ToUint8Array(options.publicKey.challenge);
排查验证步骤
- 对比前后端字节序列:后端可以打印生成的原始
ByteArray的十六进制值,前端解码后也打印Uint8Array的十六进制值,确认两者完全一致。 - 检查返回的challenge:把WebAuthn返回的challenge字符串(比如
YXNkZg)补全填充符后解码,看是否和原始字节序列一致。 - 确认传输过程无修改:检查后端返回的Base64字符串是否在前端接收时被转义、截断或修改(比如JSON序列化时的特殊字符处理)。
补充说明
WebAuthn规范要求challenge是一个字节序列,所以后端用Base64编码传输是标准做法。前端必须正确解码成Uint8Array,否则整个验证流程的challenge校验会失败。
内容的提问来源于stack exchange,提问作者Brandon Parker
相关产品推荐
相关产品推荐

