If your passkey works on one hostname but fails on another, start by comparing the RP ID with the actual calling origin.
They are not interchangeable values. An RP ID is a domain identifier. An origin includes the scheme, hostname, and port.
This is part three of Passkeys in Production, adapted from the Helpers.work RP ID guide. We build the Passkey Deployment Checker, which evaluates this public configuration boundary without claiming to test the entire login flow.
One URL, three different values
For this illustrative page:
https://login.example.com/account/security
You have:
Origin: https://login.example.com
Hostname: login.example.com
RP ID: potentially example.com or login.example.com
The path is not part of the origin. A non-default port is part of the origin, but never part of the RP ID.
Registration options use publicKey.rp.id; authentication options use publicKey.rpId. The server's expected RP ID must agree with the credential's scope.
The ordinary relationship is directional
Without Related Origin Requests, a caller can use its own effective domain or an eligible parent domain as the RP ID.
| Caller | RP ID | Ordinary relationship |
|---|---|---|
https://login.example.com |
example.com |
Eligible parent |
https://login.example.com |
login.example.com |
Same host |
https://example.com |
login.example.com |
Not an eligible parent |
https://shop.example.com |
login.example.com |
Sibling, not parent |
https://example.co.uk |
example.com |
Different domain |
A public suffix such as com or co.uk is not an acceptable shared RP ID.
This is why "just remove the first subdomain" is not a sufficient validation algorithm. Public suffix handling needs standards-aware logic.
Sibling subdomains do not always need a JSON file
If accounts.example.com and shop.example.com both use credentials registered for example.com, the ordinary relationship can be enough.
You still need:
- Credentials created for that exact RP ID.
- Correct shared account and credential mappings.
- Explicit server acceptance of the intended origins.
A broad RP ID does not authorize every subdomain in your backend. Treat delegated, abandoned, and user-controlled subdomains as part of the threat model.
Cookies and shared sessions remain separate concerns.
Different domains need explicit authorization
Suppose the browser runs at https://example.co.uk but requests RP ID example.com.
A supporting browser can use Related Origin Requests if the RP ID domain publishes:
https://example.com/.well-known/webauthn
For example:
{
"origins": ["https://example.co.uk", "https://login.example.net"]
}
These are illustrative origins. Serve your actual document publicly as JSON and list only relationships you intend to trust.
The location matters: putting this file on example.co.uk does not authorize use of the example.com RP ID.
The server still verifies the actual origin
The public allowlist permits the browser to request the credential. It does not replace the relying party's verification policy.
For the example above, the server must explicitly accept https://example.co.uk, verify the expected challenge and RP ID context, and validate the appropriate cryptographic response.
Do not fix a mismatch by accepting arbitrary origins. CORS headers and cookie-domain changes also do not substitute for WebAuthn authorization.
Five labels is not five URLs
The compatibility procedure counts registrable domain labels:
-
example.comcontributesexample. -
example.co.ukalso contributesexample. -
login.example.netalso contributesexample. -
example-rewards.comcontributesexample-rewards.
Supporting implementations must handle at least five distinct labels; they may support more. The checker evaluates that five-label baseline.
Order matters when a client exhausts its label budget. A listed origin can still fall beyond the supported limit.
Do not publish a giant list and assume every client will process all of it. For many unrelated tenant domains, a central sign-in domain may be the more maintainable design.
Debug in this order
First, inspect the actual origin where WebAuthn runs. A redirect from a marketing page can change the relevant hostname.
Second, inspect the backend-generated options. Do not assume the RP ID was discovered by a scanner; the Helpers.work tool defaults to the hostname when the RP ID is omitted.
Third, determine whether ordinary parent-domain eligibility is enough. If not, inspect the RP ID host:
curl --proto '=https' --max-time 15 --silent --show-error --include \
https://example.com/.well-known/webauthn
Replace the example domain with yours. Look for HTML fallbacks, login challenges, incorrect content types, or missing origins.
Finally, test browser support and server verification. Basic WebAuthn support does not guarantee Related Origin Requests support. A SecurityError is a diagnostic clue, not a complete root-cause report.
Domain changes are credential migrations
Existing credentials do not change RP ID when you update configuration.
Before renaming an authentication domain, decide whether to retain the old RP ID through a supported related-origin flow or guide users through authenticated re-enrollment. Keep a tested path for unsupported clients.
Continue with reading the checker report. For the broader rollout, see the deployment checklist and passkeys overview.
References: W3C related origins and web.dev's implementation guide.


Top comments (0)