Why architecture matters here

Credential architecture determines breach blast radius. With passwords, a leaked database of even well-hashed secrets invites offline cracking, and credential-stuffing turns one site's breach into every site's problem. With WebAuthn, the credential table holds public keys — leaking it hands an attacker nothing they can replay. That asymmetry is architectural, not incremental: no amount of password hashing tuning achieves it.

Phishing resistance is equally structural. One-time codes, push approvals, and TOTP all fail against a real-time proxy (evilginx-style) that relays the user's input to the genuine site; the human cannot distinguish accounts-example.com from accounts.example.com, and the secret they type works from anywhere. WebAuthn moves the origin check from the human to the browser: the client embeds the origin into the signed payload, and a signature minted on the wrong origin simply fails verification. The user cannot be tricked into producing a valid assertion for the attacker's domain, no matter how convincing the page.

Finally, the architecture decides your recovery story. A hardware-bound credential that dies with the device pushes users into account-recovery flows, which become the new weakest link. Synced passkeys answer that by replicating keys through the platform vendor's encrypted fabric — a deliberate trade: you accept the sync fabric into your threat model in exchange for users who can lose a phone without losing their account. Choosing where on that spectrum each account tier sits (synced passkeys for consumers, device-bound keys with attestation for admins) is the central design decision of a WebAuthn deployment.

Advertisement

The architecture: every piece explained

Authenticator. The trust anchor. Platform authenticators (Touch ID, Windows Hello, Android) keep private keys in a secure element or TPM; roaming authenticators (YubiKey-class) connect over USB/NFC/BLE. Keys are generated per RP and never exported — signing happens inside the boundary after user verification (biometric/PIN) or mere user presence (a touch), a distinction the RP can require via the userVerification parameter and must check in the response flags.

CTAP2. The wire protocol between client and external authenticator: CBOR-encoded commands (makeCredential, getAssertion) over USB HID, NFC, or the caBLE/hybrid transport that lets a phone serve as authenticator for a nearby laptop via QR code plus BLE proximity check — the mechanism behind cross-device passkey sign-in.

Client (browser/OS). Exposes navigator.credentials.create()/get(), validates that the calling origin is allowed to use the requested RP ID (the domain that scopes the credential — it must be a registrable suffix of the origin), assembles clientDataJSON containing the type, challenge, and origin, and hashes it into what the authenticator signs. This is the enforcement point for phishing resistance.

Relying party. Generates single-use challenges with a short TTL, stores credentialId → publicKey, signCount, transports, flags per user, and verifies assertions: signature over authenticatorData || hash(clientDataJSON), expected RP ID hash, user-verified flag, and counter monotonicity.

Discoverable credentials and sync. A discoverable (resident) credential stores the user handle on the authenticator, enabling username-less login and conditional UI — passkeys offered in the browser's autofill dropdown. The sync fabric replicates these keys end-to-end encrypted across the user's devices; attestation (an optional certificate chain at registration) lets high-security RPs verify authenticator make and model, though synced consumer passkeys generally return none.

WebAuthn / passkeys — public-key challenge-response bound to originno shared secret ever leaves the authenticatorAuthenticatorplatform / roaming keyCTAP2client-authenticator protocolBrowser / OS clientnavigator.credentialsRelying party (RP)your app serverSecure elementprivate keys never exportedSync fabriciCloud / Google PW ManagerChallenge storeserver nonces, short TTLCredential tablecredId → pubkey, counterRegistration ceremonyattestation, credential creationAuthentication ceremonyassertion, signature verifyOps — recovery paths + multi-credential + downgrade monitoring + counter anomaly alertsseal keyssyncnonceverify+storecreategetoperateoperate
WebAuthn: the browser brokers CTAP2 between an authenticator holding non-exportable keys and a relying party that stores only public keys and verifies origin-bound signatures.
Advertisement

End-to-end flow

Registration. (1) The server creates a session-bound random challenge and calls back with PublicKeyCredentialCreationOptions: RP ID and name, user id/handle, accepted algorithms (ES256 first), authenticator selection criteria, and excludeCredentials listing the user's existing credential IDs so the same authenticator is not enrolled twice. (2) The browser validates origin-vs-RP-ID, shows the platform sheet, and forwards a makeCredential to the chosen authenticator. (3) The authenticator performs user verification, generates a fresh keypair scoped to the RP ID, and returns an attestation object containing the new credential ID, the public key (COSE format), flags, and counter. (4) The server verifies the challenge matches and is unexpired, the origin is expected, the RP ID hash is correct, UV was performed if required, then parses (and optionally validates) attestation and persists credentialId, publicKey, signCount, transports against the user.

Authentication. (1) The server issues a fresh challenge in PublicKeyCredentialRequestOptions; for username-less flows allowCredentials is empty and discovery happens on the authenticator. (2) The browser (possibly via conditional UI autofill) routes getAssertion; the user verifies with biometric or PIN. (3) The authenticator signs authenticatorData || sha256(clientDataJSON) with the credential's private key and returns the assertion plus the user handle. (4) The server looks up the public key by credential ID, replays the verification checklist — challenge freshness, origin, RP ID hash, UV flag, signature validity, counter greater than stored (where counters are provided; synced passkeys often report zero) — then establishes the session. Every step is stateless except the challenge store and credential table, which makes the ceremony horizontally scalable behind ordinary load balancing.