The authentication ceremony
Signing in works the same shape as registering: a fresh challenge from the server,
a call into the real WebAuthn API, and a signature the server verifies — except this
time the authenticator signs with a key it already has, over
authenticatorData ‖ SHA-256(clientDataJSON), and the server checks that
signature against the public key it stored during registration.
1. The browser calls navigator.credentials.get()
const credential = (await navigator.credentials.get({
publicKey,
})) as PublicKeyCredential;
// 2. The server verifies the assertion
verifyAuthenticationResponse() re-derives that same byte string from
the response and checks the signature against the credential's stored public key —
and that the signature counter didn't move backwards, which is one of the ways a
cloned authenticator would get caught.
const result = await verifyAuthenticationResponse({
response,
expectedChallenge: challenge.challenge,
expectedOrigin: rp.expectedOrigin,
expectedRPID: rp.expectedRPID,
credential: {
id: credItem.credentialId,
publicKey: isoBase64URL.toBuffer(credItem.publicKey),
counter: credItem.counter,
transports: credItem.transports as AuthenticatorTransportFuture[],
},
});
// Try it
This needs a passkey already registered in your current demo session — head to Registration first if you haven't yet.