Error message
Unexpected registration response origin "http://localhost:3000", expected "https://app.example.com"
Also printed as
- Unexpected authentication response origin "http://localhost:3000", expected "https://app.example.com"
- Unexpected registration response challenge "b3RoZXI", expected "c2VydmVyLWNoYWxsZW5nZS0xMjM"
The first quoted value is the origin the browser reported, the second your configuration. During sign-in the text says "authentication response"; a mismatched challenge produces "Unexpected registration response challenge …" from the same library.
SimpleWebAuthn (@simplewebauthn/server) — verifyRegistrationResponse / verifyAuthenticationResponse
What it means
The browser recorded the page's origin in `clientDataJSON` (the data the authenticator signs), and it differs from the `expectedOrigin` your server passed to SimpleWebAuthn. The check exists to stop a credential ceremony performed on one site — for example a phishing look-alike — from being accepted by another. Nothing is wrong with the authenticator; the server's notion of where its pages live does not match where the ceremony actually ran.
Why it happens
Production origin configured in development (or vice versa)
common`expectedOrigin` hard-coded to the production URL while testing on `http://localhost:3000`, a preview deployment or a tunnel (ngrok, Cloudflare Tunnel) URL.
The sign-in page is served from several origins
common`www.` and apex domain, several country domains, or a separate `accounts.` subdomain. Each origin that can run the ceremony must be listed.
A native Android app
occasionalCredential Manager reports `android:apk-key-hash:…` as origin, not your web origin. The app's signing-key hash must be in `expectedOrigin`.
How to fix it
- 1.
List every legitimate origin explicitly
`expectedOrigin` accepts an array. Keep it to origins you control; never accept the origin from the request itself.
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.
Add the Android app's origin
Compute the base64url SHA-256 of the app's signing certificate and add it in the `android:apk-key-hash:` form.
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", });