WebAuthn navigator.credentials.create() with excludeCredentialsWebAuthn 与通行密钥
错误信息
InvalidStateError: The user attempted to register an authenticator that contains one of the credentials already registered with the relying party.
上面是 Chromium 的文本;其他浏览器的措辞不同。DOMException 名称 `InvalidStateError` 是固定不变的部分。
WebAuthn navigator.credentials.create() with excludeCredentials — DOMException InvalidStateError (Chromium)
含义
注册被拒绝,因为认证器中已有你在 `excludeCredentials` 中列出的某个凭证。依赖方发送这个列表,正是为了防止同一个认证器为同一个账号注册两次,所以这通常就是预期的结果——用户在这台设备上已经有一个可用的通行密钥。浏览器只有在用户与认证器交互之后才会透露这一点。
原因
用户在同一台设备上再次点击了“添加通行密钥”
常见最常见的情况。该账号在这个认证器上已经有凭证;正确的做法是提示用户用它登录。
同步通行密钥已出现在这台设备上
常见iCloud 钥匙串、Google 密码管理工具等通行密钥提供方会同步凭证。在手机上注册的通行密钥同样存在于笔记本电脑上,因此“为笔记本电脑注册”会被排除。
排除列表中包含其他用户的凭证
偶见如果有 bug 把所有凭证(或共用这台设备的另一个账号的凭证)都列了进去,本应成功的注册就会被阻止。
修复方法
- 1.
告知用户已完成设置
把 InvalidStateError 视为“这里已经有通行密钥”,并引导用户用它登录。
js try { const credential = await navigator.credentials.create({ publicKey: options }); await saveCredential(credential); } catch (e) { if (e.name === "InvalidStateError") { showMessage("This device already has a passkey for your account. You can use it to sign in."); } else if (e.name !== "NotAllowedError") { throw e; } } - 2.
只排除当前用户的凭证
用已登录用户已存储的凭证 ID 构建 `excludeCredentials`,并为每个账号使用固定不变的随机 `user.id`。
js // Server: exclude only the CURRENT user's credentials (base64url ids from your database). const options = await generateRegistrationOptions({ rpName: "Example", rpID, userName: user.email, userID: user.webauthnUserId, // stable per user, never the email excludeCredentials: user.passkeys.map((p) => ({ id: p.credentialId, transports: p.transports })), });