Lab · 10 / 16
A key that cannot be phished.
A passkey replaces the password with a key pair. The private key never leaves the device, and the browser only uses it on the site it was created for.
- Authenticator
- phone, laptop or security key
- Browser
- checks the website’s origin
- Server
- the site, called relying party
- "Create a passkey"
Step 1 of 5
The user asks for a passkey
In plain words You tell the building you want a key instead of a password at the door.
The user is already signed in, for example with a password and MFA, and chooses to add a passkey to the account.
- creation options · challenge
Step 2 of 5
The server sends the rules
In plain words The building hands you a blank key with its name engraved on it.
The server sends a random challenge, its own identity (rp.id), an id for the user and the algorithms it accepts. It also asks for user verification: a fingerprint, face or PIN.
{ "challenge": "Y2hhbGxlbmdlLTEyMzQ", "rp": { "id": "example.com", "name": "Example" }, "user": { "id": "dXNlci0yNDgy", "name": "ada@example.com" }, "pubKeyCredParams": [{ "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 }], "authenticatorSelection": { "residentKey": "required", "userVerification": "required" } } - navigator.credentials.create()
Step 3 of 5
The browser asks the device
In plain words The browser checks you are really in that building before letting the key be cut.
The browser checks that rp.id matches the site in the address bar and adds the real origin to clientDataJSON. A look-alike domain cannot ask for a key for example.com.
- fingerprint → new key pair
Step 4 of 5
A key pair is born
In plain words Your phone cuts the key and keeps the original in a safe only your fingerprint opens.
After the fingerprint or face check, the authenticator creates a new key pair just for this site. The private key stays in secure hardware; only the public key and a credential id come out.
- attestationObject + clientDataJSON
Step 5 of 5
The server saves the public key
In plain words The building keeps only a mould of the lock, which is useless to a thief.
The server checks the challenge, the origin and the rpIdHash, then stores the credential id and public key. If its database leaks, there is nothing to replay: no password, no secret.
clientDataJSON = { "type": "webauthn.create", "challenge": "Y2hhbGxlbmdlLTEyMzQ", ✓ "origin": "https://example.com" ✓ } stored: credentialId = "AdKX…9fQ", publicKey = COSE(ES256), signCount = 0
Flow complete.
Example domains and values. Nothing leaves your browser.