跳到主要内容
ecdsa.com
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` 中列出的某个凭证。依赖方发送这个列表,正是为了防止同一个认证器为同一个账号注册两次,所以这通常就是预期的结果——用户在这台设备上已经有一个可用的通行密钥。浏览器只有在用户与认证器交互之后才会透露这一点。

原因

修复方法

  1. 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. 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 })),
    });

相关错误

浏览完整的签名错误库