Summary
Follows the Passkey Central required pattern for signing in with a passkey: autofill/conditional UI, a working identifier-first fallback, and discoverable credentials that autofill can find.
Stacked on #212. This branch is based on security-review-fixes because both
touch the sign-in handlers. Retarget it to main once #212 lands.
What changed
- Server:
LoginBeginwith no username startsBeginDiscoverableLogin.LoginFinishusesFinishDiscoverableLogin, finds the account by credential id, and refuses a user handle that does not match it. The account is only resolved after the assertion, so an empty begin reveals nothing about a name. - Browser: when
PublicKeyCredential.isConditionalMediationAvailable()reports support, the login page starts a conditional ceremony and callsnavigator.credentials.get({publicKey, mediation: "conditional"}). A saved passkey can then be selected from the account field. Cancellation and a missing passkey are silent; the form remains the fallback. - Registration: invitation, self-registration, and adding a passkey now ask
for
residentKey: required. New passkeys are discoverable, user verification stays required, and attestation stays none. - Docs: the browser sign-in section explains autofill, discoverable credentials, and the username-first fallback for a passkey created before this change.
The template already had autocomplete="username webauthn"; the missing part was
the conditional ceremony and a discoverable server flow.
Tests
- Empty-username begin returns no
allowCredentialsand requires user verification. - Registration options ask for a resident key and user verification.
- The existing username-first flow, the #209 input bounds, and the #212 cache changes still pass.
vole workflow check and vole workflow run verify pass. Conditional UI itself
was checked by syntax and code review; a real browser/authenticator test is still
manual because the test suite has no virtual authenticator.