エラーメッセージ
Unexpected registration response origin "http://localhost:3000", expected "https://app.example.com"
別の表記
- Unexpected authentication response origin "http://localhost:3000", expected "https://app.example.com"
- Unexpected registration response challenge "b3RoZXI", expected "c2VydmVyLWNoYWxsZW5nZS0xMjM"
引用符で囲まれた最初の値はブラウザーが報告したオリジン、2 番目は設定した値です。ログイン時には、文言が「authentication response」になります。チャレンジが一致しない場合は、同じライブラリから「Unexpected registration response challenge …」が出力されます。
SimpleWebAuthn (@simplewebauthn/server) — verifyRegistrationResponse / verifyAuthenticationResponse
このエラーの意味
ブラウザーはページのオリジンを `clientDataJSON`(認証器が署名するデータ)に記録しましたが、その値が、サーバーが SimpleWebAuthn に渡した `expectedOrigin` と異なっています。このチェックは、あるサイト(たとえば本物そっくりのフィッシングサイト)で行われた認証情報のセレモニーが、別のサイトで受け入れられるのを防ぐためのものです。認証器には問題ありません。サーバーが想定しているページの場所と、セレモニーが実際に実行された場所が一致していないのです。
発生する理由
開発環境に本番環境のオリジンが設定されている(またはその逆)
よくある`http://localhost:3000`、プレビューデプロイ、トンネル(ngrok、Cloudflare Tunnel)の URL でテストしているのに、`expectedOrigin` が本番環境の URL にハードコードされています。
ログインページが複数のオリジンから配信されている
よくある`www.` 付きのドメインと apex ドメイン、複数の国別ドメイン、独立した `accounts.` サブドメインなどです。セレモニーを実行できるオリジンは、すべて列挙しなければなりません。
ネイティブの Android アプリ
ときどき認証情報マネージャー(Credential Manager)は、Web のオリジンではなく `android:apk-key-hash:…` をオリジンとして報告します。アプリの署名鍵のハッシュを `expectedOrigin` に含めなければなりません。
修正方法
- 1.
正当なオリジンをすべて明示的に列挙する
`expectedOrigin` には配列を渡せます。自分が管理するオリジンだけに限定し、リクエスト自体に含まれるオリジンを決して受け入れてはいけません。
js const verification = await verifyRegistrationResponse({ response: body, expectedChallenge: session.currentChallenge, // Every origin that legitimately hosts your sign-up page, exactly as browsers report it: expectedOrigin: ["https://app.example.com", "https://www.example.com"], expectedRPID: "example.com", }); - 2.
Android アプリのオリジンを追加する
アプリの署名証明書の SHA-256 ハッシュを base64url で求め、`android:apk-key-hash:` の形式で追加してください。
js // Android apps report "android:apk-key-hash:<base64url SHA-256 of the app signing certificate>". // SHA-256 fingerprint from Play Console or: keytool -list -v -keystore release.jks | grep SHA256 const fingerprintHex = process.env.ANDROID_SIGNING_CERT_SHA256.replaceAll(":", ""); const androidOrigin = "android:apk-key-hash:" + Buffer.from(fingerprintHex, "hex").toString("base64url"); const verification = await verifyRegistrationResponse({ response: body, expectedChallenge: session.currentChallenge, expectedOrigin: ["https://example.com", androidOrigin], expectedRPID: "example.com", });