The Webauthn authentication ceremony, and what to store

Registration gave you a public key. Authentication is the proof that the same private key is still on the other side.

The exchange

  1. Your server issues request options: a fresh challenge, and optionally the list of credentials you accept.
  2. The authenticator signs the challenge and the authenticator data.
  3. Your server verifies the signature against the stored public key, and checks the challenge, the origin, the relying party hash and the flags.

What to store, and why

  • The credential identifier. How you find the record when a credential presents itself.
  • The public key, in COSE form. Keep the original bytes: re-encoding CBOR breaks signatures.
  • The sign counter. See below.
  • The user handle, to support username-less sign-in.
  • A label and a creation date, so the user can recognise and revoke a device.

That last one is not cosmetic. Somebody who loses a laptop needs to remove the right credential without removing all of them.

The sign counter, handled sanely

An authenticator may increment a counter at each use, so a server can spot a cloned key: a value lower than the one stored means two copies exist.

Synchronised passkeys generally report zero and never move. Treat a counter of zero as « not supported », not as an alarm, or you will lock out the majority of your users. Enforce the check only when the stored value is already non-zero.

Conditional UI

Rather than a « sign in with a passkey » button, the browser can offer the credential inside the login field itself, alongside saved passwords. It requires discoverable credentials, so ask for residentKey: preferred at registration if you want that later.