RP ID Strategy for Multi-Domain Apps
The RP ID decides which of your surfaces share credentials, and it is effectively permanent — a credential is bound to the value in force when it was created. This page covers how to choose it from your account model rather than from your DNS layout, what widening it later actually costs, and how related origins change the picture for multi-domain brands. It sits under Relying Party and Authenticator Roles, where the suffix rule itself is defined.
Constraint Reference
| Constraint | Rule | Consequence |
|---|---|---|
| Suffix relationship | the RP ID must equal the page’s effective domain or be a registrable parent | a child RP ID on a parent page is refused |
| Public suffix boundary | the RP ID cannot be a public suffix | hosts on *.pages.dev and similar cannot widen |
| Binding at creation | fixed for the life of the credential | widening strands existing credentials |
| Per-origin visibility | credentials are offered only for a matching RP ID | separate RP IDs mean separate account silos |
The first two are enforced by the browser and produce a SecurityError. The last two are not errors at all — they are silent consequences that surface as “my passkey has disappeared”.
Root Cause Analysis
1. Choosing from the DNS tree rather than the account model. The question is not which domains you own, it is which surfaces share an account. If a user has one account across app.example.com and accounts.example.com, those surfaces must share one RP ID, and the shared registrable domain is the only value that works.
2. Starting narrow and planning to widen. This is the decision that cannot be undone. Every credential enrolled under app.example.com becomes invisible the moment you deploy example.com, and the only recovery is mass re-enrolment through the recovery flow — which is both slower and weaker than the credential you just invalidated.
3. Hosting on a public suffix. Preview and static-hosting platforms frequently place each deployment on a host whose registrable domain is the whole host. Credentials on such a host cannot be shared with anything, and each preview deployment is its own relying party. This is fine for previews and fatal if it reaches production.
4. Deriving the RP ID from the request. A service that reads the value from an incoming header will build ceremony options for whatever a proxy or an attacker put there. Pin the value in configuration, per environment.
Step-by-Step Resolution
Step 1 — enumerate the surfaces that share an account
List every origin from which a user of one account may sign in. If the list spans several subdomains of one registrable domain, the answer is that domain.
Step 2 — check the public suffix boundary
Confirm your chosen value is not itself a public suffix, and that every listed origin is a subdomain of it. Preview hosts frequently fail this check.
Step 3 — pin it per environment
Production, staging and local development each need their own configured value, validated at process start. A service whose RP ID is not a suffix of every allowed origin should refuse to boot.
Step 4 — separate brands deliberately
Where two products genuinely have separate accounts, separate RP IDs are the correct choice and the resulting isolation is a feature. Document that decision so a future engineer does not “fix” it.
Step 5 — treat related origins as an enhancement
Where one brand spans unrelated domains, a related-origins configuration can let them share an RP ID. Support is uneven, so design the account model to work without it and let it improve the experience where available.
Verification and Testing
Assert the suffix relationship in a unit test that reads your real configuration. For each environment, every allowed origin must be equal to or a subdomain of the configured RP ID; anything else is a deployment that cannot work.
Add a synthetic ceremony per production surface. A headless run that completes a registration and an assertion on each sign-in origin catches the case where a new surface was added to the estate and nobody updated the allow-list or checked the suffix relationship.
For a multi-brand estate, add a negative test: a credential enrolled under one RP ID must not be offered on a surface using the other. That test documents the intended isolation in a form that survives refactoring.
Pitfalls
1. Using a preview hostname in production configuration. It works in preview and silos every credential.
2. Widening after launch. There is no migration; there is only re-enrolment.
3. Assuming related origins are universally supported. Design for the case where they are not.
Frequently Asked Questions
Can two RP IDs share one user account internally?
Yes, and it is sometimes the pragmatic answer during a transition: your database links both credentials to one account even though the authenticator treats them as separate relying parties. The user still has to enrol twice, and the sign-in surface must know which RP ID to request, but nothing about the account model forces a split.
Does the RP ID affect the origin check?
No — they are separate checks with different rules. The origin is compared as an exact string against an allow-list; the RP ID is compared with a suffix rule. Both must pass, and treating either as a substitute for the other produces a false rejection or a real weakness.
What should development use?
localhost, which is a secure context without TLS and is its own registrable domain. Avoid LAN addresses, which are neither, and avoid sharing the production RP ID with a development surface, which would let a development origin claim production credentials if the allow-list ever slipped.
Can I use a country-code domain as the RP ID for all locales?
Only if every locale surface is a subdomain of it, which is usually not the case for example.de and example.fr. That is precisely the shape related origins exist to serve; without them, either pick one canonical domain and serve all locales beneath it, or accept per-locale credential silos.
How do native apps fit into this?
Through the platform’s associated-domains mechanism, which links an application to a domain so that credentials created for that RP ID are available in the app. The RP ID stays the web domain; the app does not get one of its own, and this is another reason to choose the value carefully at the outset.
Is there any reason to prefer a narrow RP ID?
Only genuine isolation — a product whose accounts must not be interchangeable with a sibling’s. If the two products share a login, a narrow RP ID gives you all of the costs of separation and none of its benefits.
How do acquisitions and rebrands affect this?
They are the situation where the permanence bites hardest. Acquiring a product with its own RP ID leaves you with two credential silos and no way to merge them, so the realistic plan is to keep both working, enrol new credentials under whichever RP ID the combined account model settles on, and retire the other over a period measured in quarters rather than sprints. Rebranding to a new domain has the same shape and the same answer: run both, prompt for a new credential after a successful sign-in, and let the old value expire with the accounts that still depend on it.
Related
- Relying Party and Authenticator Roles — the parent topic area and the suffix rule
- Resolving WebAuthn SecurityError — what a wrong RP ID looks like at runtime
- WebAuthn Security Boundaries for Enterprise Apps — the origin boundary in a multi-surface estate
- Validating clientDataJSON Fields — the exact-match origin check that runs alongside the RP ID rule