エラーメッセージ
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 パスワードマネージャーなどのパスキープロバイダーは、認証情報を同期します。スマートフォンで登録したパスキーはノートパソコンにも存在するため、「ノートパソコン」の登録は除外されます。
除外リストにほかのユーザーの認証情報が含まれている
ときどきすべての認証情報(またはデバイスを共有している別のアカウントの認証情報)を列挙するバグがあると、本来成功するはずの登録がブロックされます。
修正方法
- 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.
現在のユーザーの認証情報だけを除外する
`excludeCredentials` は、ログイン中のユーザーについて保存されている認証情報 ID から作成し、`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 })), });