WebAuthn 是 JavaScript API 驱动的认证流程,非 HTML 属性可启用;需用 isUserVerifyingPlatformAuthenticatorAvailable() 或轻量注册试探可用性;authenticatorSelection 须按场景选 platform/cross-platform;allowCredentials 中 ID 必须 base64url 解码为 Uint8Array;响应数据须 base64url 序列化后 JSON 提交;challenge 必须服务端生成、一次性、字节级严格匹配。

WebAuthn 不是“在 HTML 里加个属性就能启用”的功能,它本质是一套 JavaScript API 驱动的认证流程,HTML 表单只负责触发、承载和传递数据。直接往 <form> 标签里写 webauthn="true" 这类属性完全无效。
怎么判断浏览器是否真能用 WebAuthn
光检查 PublicKeyCredential 存在还不够——它只说明浏览器支持该标准,不代表当前设备有可用认证器(比如没配指纹、没插安全密钥、或系统禁用了 Windows Hello)。必须额外验证用户验证能力:
- 调用
navigator.credentials.create()或.get()前,先做PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()检查(Chrome/Edge 支持,Safari 和 Firefox 目前不返回准确结果) - 更稳妥的做法:直接发起一次轻量注册尝试(带
userVerification: "discouraged"),捕获NotAllowedError或SecurityError来反推是否可用 - 不要依赖
navigator.credentials是否为undefined做 UI 显示逻辑——某些旧版 Safari 会暴露该对象但实际调用失败
注册时 authenticatorSelection 怎么选
这个配置项直接影响用户看到什么提示、用什么方式完成注册,选错会导致“点了按钮没反应”或“弹出 USB 密钥提示但用户只有指纹”:
-
authenticatorAttachment: "platform"→ 强制走本机生物识别(Windows Hello / Touch ID / Face ID),不显示 USB 密钥选项;适合想默认启用指纹/面容的场景 -
authenticatorAttachment: "cross-platform"→ 只接受可移动认证器(YubiKey 等),排除系统级生物识别;适合企业硬件密钥管理 -
userVerification: "required"→ 每次都强制刷指纹/输 PIN,即使设备已解锁;"preferred"则由认证器自行决定是否验证用户(例如已登录 Windows 的用户可能跳过指纹) - 别漏掉
requireResidentKey: true(如果需要离线认证)——否则部分平台认证器可能不生成可检索的凭证 ID
认证请求里 allowCredentials 为什么总报错
常见错误是后端返回的凭证 ID 编码格式和前端传入的不一致,导致 navigator.credentials.get() 直接拒绝执行:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 服务器存的 ID 通常是 base64url 编码字符串(如
"B5q...XzA"),前端必须转成Uint8Array才能放进allowCredentials[0].id - 转换不能用
atob()直接解 base64 —— base64url 和 base64 字符集不同(-替代+,_替代/,无填充);推荐用base64url-to-buffer类库或手写补全逻辑 - 如果用户有多个凭证(比如手机 + 笔记本各注册一次),
allowCredentials数组必须包含全部 ID,否则浏览器可能随机选一个不匹配的来尝试,最终失败 - 前端传空数组或
undefined会让浏览器忽略白名单,降级为枚举所有本地凭证——看似能过,但失去服务端控制权,也容易触发钓鱼防护拦截
表单提交后怎么把 WebAuthn 结果发给后端
WebAuthn 的响应对象(newCredential 或 assertion)是二进制密集型结构,不能直接塞进 <input type="hidden"> 提交——会丢失字节或被 HTML 转义破坏:
- 必须在 JS 中序列化:对
response.clientDataJSON、response.authenticatorData、response.signature等字段分别调用bufferToBase64url()(注意是 base64url,不是 base64) - 把这些字符串作为 JSON 字段 POST 到后端,例如
{ "id": "...", "rawId": "...", "response": { "clientDataJSON": "...", "authenticatorData": "...", "signature": "..." } } - 别试图用
FormData.append()传ArrayBuffer——多数后端框架不支持原生解析,且 multipart body 容易损坏二进制内容 - 表单本身可以保留传统结构(如
<form id="login-form">),但提交行为必须event.preventDefault(),全程由 JS 控制流程
最容易被忽略的是挑战值(challenge)的生命周期管理:它必须是服务器生成的、一次性的、带足够熵的 Uint8Array(32),且必须和服务端存储的原始值字节级完全一致——哪怕多一个空格、少一个字节,签名验证就会失败,而错误信息往往只显示 “Invalid signature”,没有具体哪一步出问题的线索。


















