WebAuthn / FIDO2 Overview
Passkeys use public-key cryptography. The private key never leaves the device; the server stores only the public key.
Registration Flow
- Server generates a random challenge and returns it along with
rp(relying party) info. - Browser calls
navigator.credentials.create()with the challenge. - Authenticator generates an asymmetric key pair; private key stored in secure enclave.
- Authenticator returns an attestation object containing the public key, credential ID, and signed challenge.
- Server validates attestation and stores the credential.
// Client-side (simplified)
const credential = await navigator.credentials.create({
publicKey: {
challenge: base64ToArrayBuffer(serverChallenge),
rp: { name: "Example", id: "example.com" },
user: { id: userId, name: userEmail, displayName: userName },
pubKeyCredParams: [{ type: "public-key", alg: -7 }] // ES256
}
});
// POST credential.response to /webauthn/register
Authentication (Assertion) Flow
- Server generates a fresh challenge and returns allowed credential IDs for the user.
- Browser calls
navigator.credentials.get(). - Authenticator signs the challenge with the stored private key.
- Server verifies the signature against the stored public key and checks the sign count.
// Client-side (simplified)
const assertion = await navigator.credentials.get({
publicKey: {
challenge: base64ToArrayBuffer(serverChallenge),
allowCredentials: [{ type: "public-key", id: credentialId }]
}
});
// POST assertion.response to /webauthn/authenticate
Data Model
Credential Table
Credential (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT,
credential_id BYTEA UNIQUE, -- opaque handle from authenticator
public_key BYTEA, -- COSE-encoded public key
aaguid UUID, -- authenticator model identifier
sign_count INT DEFAULT 0, -- replay protection
device_name TEXT, -- user-provided label
created_at TIMESTAMP,
last_used_at TIMESTAMP
)
Challenge Store (Redis)
SET challenge:{session_id} <base64_challenge> EX 300 -- 5-minute TTL
Registration Validation
- Verify attestation statement format and signature.
- Check
originmatches expected RP origin. - Verify
rpIdHashin authenticatorData matches SHA-256 of the RP ID. - Confirm the challenge in clientDataJSON matches the stored challenge.
Assertion Validation
- Re-verify rpIdHash and origin.
- Verify signature over
authenticatorData || SHA-256(clientDataJSON)using stored public key. - Assert
sign_count > stored_sign_countto detect cloned authenticators; update stored value.
Multiple Passkeys and Sync
Users may register multiple credentials (phone + laptop). Synced passkeys are backed up via iCloud Keychain or Google Password Manager, identified by aaguid. Platform authenticators set the BS (backup state) flag in authenticatorData.
Fallback Flow
Users without a registered passkey fall back to password + 2FA. The registration prompt is shown post-login to encourage passkey adoption.
See also: Apple Interview Guide 2026: iOS Systems, Hardware-Software Integration, and iCloud Architecture
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering