Headline: A passkey is a WebAuthn public-key credential held by the device's authenticator and permanently bound to one Relying Party ID (RP ID), and it replaces the password and the second factor in a single prompt. Three details caused nearly every bug I hit: the RP ID must be the origin's registrable domain or a parent of it, user.id must be an opaque handle rather than an email address, and the browser's autofill passkey chip only appears when you call navigator.credentials.get() with mediation: 'conditional'.
Key takeaways
RP ID is permanent and one-directional. A passkey registered with rp.id: 'example.com' is usable from app.example.com. A passkey registered with rp.id: 'app.example.com' is never usable from example.com. Pick the apex domain before your first user registers.
user.id is an opaque account handle, not an identifier. The WebAuthn spec caps it at 64 bytes and states it must not contain personally identifying information. An email address there is baked into the credential and cannot be rotated.
Usernameless sign-in requires a discoverable credential, requested with authenticatorSelection.residentKey: 'required'. Without it, authentication must send an allowCredentials list, which means you need the username first.











