Migrating passkeys to a new domain
Changing domains is usually when developers realise their user’s passkeys only work on the existing domain 💥
A passkey created for oldsite.com is not automatically available on newsite.com. That domain binding is part of what makes passkeys resistant to phishing, but it also means a rebrand, acquisition, or infrastructure move can break sign-in if you treat passkeys like portable usernames and passwords.
Related origin requests are the standards-based answer. They let newsite.com ask the browser for a passkey that was originally created for oldsite.com, but only if oldsite.com explicitly trusts the new origin.
Why domain migration gets awkward quickly
Section titled “Why domain migration gets awkward quickly”The browser piece is only part of the problem. A real migration usually forces you to answer several application-level questions:
- Should you keep issuing passkeys against
oldsite.com, or switch new registrations tonewsite.com? - How will
oldsite.compublish a valid/.well-known/webauthnfile over HTTPS? - How will you decide whether a given login attempt should request
oldsite.comornewsite.com? - How will you track which users still have legacy passkeys?
- When a user finally registers a new-domain passkey, how will you retire the old one?
That is where developers usually end up writing migration-specific authentication logic.
What the raw standards require
Section titled “What the raw standards require”The core WebAuthn rule is simple: the source domain must explicitly trust the calling origin.
If you want newsite.com to authenticate passkeys that were created for oldsite.com, then oldsite.com needs to host this file:
{ "origins": [ "https://oldsite.com", "https://newsite.com" ]}That solves only the browser permission check.
You still need to choose a migration strategy. The two main options are covered in our docs guide to Related origin requests:
- Keep using
oldsite.comas the rpID indefinitely, even fromnewsite.com. - Switch the tenancy to
newsite.comfor all new registrations, while continuing to accept legacyoldsite.compasskeys during the transition.
In practice the second option is usually what people mean by a domain migration.
Where Passlock takes care of the complexity
Section titled “Where Passlock takes care of the complexity”Passlock already documents the low-level rules in Migrating passkeys to a new domain. What Passlock adds is the application plumbing around those rules.
1. Configure the new steady state in the Passlock console
Section titled “1. Configure the new steady state in the Passlock console”Once you are ready to start issuing passkeys for the new domain, update the tenancy so the primary rpID is newsite.com and oldsite.com remains available as a related origin:
New registrations now use the new domain, while legacy passkeys can still be accepted.
After that, your normal Passlock registration flow continues to work. New passkeys are created against the tenancy’s current rpID, so you do not need a separate migration-specific registration path.
2. Ask for the legacy rpID only when you need it
Section titled “2. Ask for the legacy rpID only when you need it”The awkward part of migration is authentication, not registration.
A browser cannot present passkeys from oldsite.com and newsite.com in one combined request. Your application has to decide which rpID to ask for.
Passlock exposes that decision as a simple override on the normal browser helper:
import { authenticatePasskey } from "@passlock/browser";
await authenticatePasskey({ tenancyId: "myTenancyId", rpId: "oldsite.com",});That is the main benefit during migration. You do not need to hand-roll WebAuthn option endpoints or custom browser ceremony code for the legacy case. You keep using authenticatePasskey(), and only switch the rpID when your flow determines the user is still on the old domain.
3. Use Passlock’s credential metadata to drive the migration
Section titled “3. Use Passlock’s credential metadata to drive the migration”The hardest operational part is usually knowing which passkeys are still legacy credentials.
Passlock keeps the underlying WebAuthn credential mapping, including the credential’s rpId, available through its APIs. The Get passkey response includes credential.rpId, which lets your backend reason about whether a passkey belongs to oldsite.com or newsite.com.
import { getPasskey } from "@passlock/server";
const passkey = await getPasskey({ tenancyId: "myTenancyId", apiKey: "myApiKey", passkeyId: "myPasskeyId",});
if (passkey.credential.rpId === "oldsite.com") { // Offer the legacy login path or prompt for a replacement passkey}That same metadata is also what Passlock uses for device-side maintenance operations such as updating or deleting passkeys. You do not need to manually preserve raw WebAuthn identifiers just to make the migration workable later.
4. Replace and retire old passkeys with the same library surface
Section titled “4. Replace and retire old passkeys with the same library surface”Once a user has authenticated with a legacy passkey, the migration path becomes straightforward:
- Let them complete sign-in using
authenticatePasskey({ rpId: "oldsite.com" }). - Prompt them to register a fresh passkey on
newsite.com. - Delete the old passkey from your vault and, where supported, from the local device.
Passlock already has docs and helpers for the cleanup step, including backend and device passkey deletion. That means the migration does not stop at “the user managed to sign in once”; you can actually move them onto the new domain and retire the legacy credential.
A practical migration flow
Section titled “A practical migration flow”If you are migrating from oldsite.com to newsite.com, the typical Passlock flow looks like this:
- Update your Passlock tenancy so new registrations use
newsite.com. - Add
newsite.comtohttps://oldsite.com/.well-known/webauthn. - Keep standard registration on the new domain for all new or replacement passkeys.
- Use a two-step authentication flow or a dedicated “Sign in with your old domain passkey” action when a user may still be carrying an
oldsite.comcredential. - After a successful legacy login, register a replacement passkey on
newsite.com. - Retire the old passkey once the new one is confirmed.
That leaves each piece of complexity in the right place:
- The standards details live in the Related origin requests guide.
- The application helpers live in
@passlock/browserand@passlock/server. - The credential metadata needed for migration stays available through Passlock’s REST API and server helpers.