WebAuthn Credentials Are Domain-Bound
A WebAuthn credential is bound to the relying-party ID — the domain it was registered under. That binding is part of the credential itself, not a setting alongside it.
Change the domain and every registered credential becomes worthless. Not degraded, not migratable: worthless. Every user re-registers their hardware.
Why this is sharper than it sounds
The binding is deliberate, and it's the reason passkeys resist phishing. A credential registered for id.example.com will not be offered on id-example.com, because the browser refuses to match them. The user cannot be talked into making that mistake, because the check happens below the layer where persuasion works.
The property that makes the credential unphishable is exactly the property that makes it immovable. You don't get one without the other.
The part that catches people
This decision is made at first deploy, usually without being recognised as a decision.
You pick a hostname for the login service because you need to put it somewhere. Nothing warns you. Everything works. The choice looks like a DNS detail, filed mentally alongside the reverse-proxy config.
Then it sets. And it sets harder than almost anything else in the deployment:
- Change your database? Dump and restore.
- Change your hosting provider? Move the volumes.
- Change your identity provider? Export users, import users, reconfigure the clients.
- Change the domain your credentials were registered under? Contact every user and ask them to re-enrol their security keys, one at a time, from a login page they can no longer log into with the thing they'd normally use.
That last one isn't a data migration problem. There's no data to move. It's a coordination problem with every human who ever registered, and the fallback path is whatever recovery mechanism you set up — which is now load-bearing for your entire user base simultaneously.
Which means the hostname is an architectural commitment
Worth stating as a rule: the hostname you serve WebAuthn from is a long-term commitment, and it should be chosen with the same seriousness as a data model.
Practically:
- Pick a hostname you can keep independent of the software behind it. A domain named after the function survives replacing the product; one named after the product does not.
- Understand the relationship between the RP ID and origin before first registration, not after.
- Treat "we might move this later" as a reason to choose more carefully now, not as a reason to defer.
Seen in the wild
This isn't theoretical caution. A concrete instance: the Kanidm deployment behind this site's services carries an explicit warning in its own module — domain and origin are pinned at first deploy to id.byteflavour.dev and must never change afterwards, because Kanidm bakes the domain into every WebAuthn credential it issues.
Notably, that warning had to be written into the deployment to be visible at the moment it mattered. The constraint comes from the standard, but nothing in the act of deploying surfaces it on its own.
The consequence downstream
This is why replacing an identity provider is much harder than its feature list suggests. Comparing two products on capabilities implies you could swap one for the other. If passkeys are registered against the current one's domain, you cannot — at least not without an event that touches every user.
It's a live constraint here: it's one of the two reasons the plan in Questions to Ask an IdP adds a second system rather than migrating to it. The other reason — existing services already depending on it — would eventually expire as those services were reconfigured. This one wouldn't.
Where this actually stands
Budding. The mechanism is standardised and settled; nothing here is expected to change. What would improve this note is the other side of it — what a domain migration actually costs when someone has to run one, how well recovery codes carry the load, and whether related-origin requests meaningfully soften any of it. None of that has been tested, so none of it is claimed.