Why
The Passkey Central required pattern for signing in with a passkey asks for
autofill/conditional UI and a graceful identifier-first fallback. Vole already has
autocomplete="username webauthn" on the account field, but the page never starts
a conditional ceremony: auth.js only opens a passkey prompt after a form submit
with a username, and LoginBegin requires a username. A discoverable passkey
cannot be used without typing a name.
New passkeys are also registered with residentKey: preferred, so some
authenticators may create credentials that autofill can never find.
Scope
- Server:
LoginBeginwith no username starts a discoverable login ceremony.LoginFinishusesFinishDiscoverableLogin, resolves the account by credential id, and refuses a user handle that does not match that account. The username-first flow stays for older or non-discoverable passkeys. - Browser: on the login page, if
PublicKeyCredential.isConditionalMediationAvailable()is true, start a conditional ceremony on first interaction with the account field and callnavigator.credentials.get({publicKey, mediation: "conditional"}). Successful autofill finishes the ceremony. The existing form remains the fallback for browsers without autofill and for a passkey that is not discoverable. - Registration: invitation, self-registration, and adding a passkey require a
discoverable credential (
residentKey: required). User verification stays required and attestation stays none. - Tests: an empty-username begin returns no allowed credentials; registration options ask for a resident key; the existing username-first behavior and the #209 bounds still hold.
- Docs: record the autofill behavior and the fallback for an older passkey that is not discoverable.
Acceptance
- A browser that supports conditional UI can offer a saved passkey in the account field and sign in without typing a name.
- The username-first form still signs in, including for passkeys created before this change.
- New passkeys are discoverable and require user verification.
- The empty-username begin does not reveal whether a name exists and stays within the existing rate limits and ceremony bounds.
Related: #212 (the auth input bounds this builds on).