feat(login): usernameless passkey sign-in - #108
Merged
Conversation
Contributor
🧪 Test Summary
|
yahyafakhroji
added a commit
that referenced
this pull request
Jul 31, 2026
… hardening MEDIUM: - passkey-discover: reorder getUser → live-session guard → listAuthMethods (the local cookie guard now trips BEFORE the second provider round-trip) - passkey_discover audit events: success (hashed actor) + per-reason failures (disabled/invalid_input/no_user_handle/unresolved_user/live_session/ no_passkey_method/mint_failed/challenge_failed) — enumeration parity stays caller-facing only - AUTH_PASSKEY_DISCOVERY_ENABLED kill switch (env, default ON; 'false'/'0' disables) gating the loader arm + the discover action; documented in .env.example LOW: - PasskeyDiscoverData moved to a shared types-only module (route + hook) - decodeUserHandle: strict base64url charset gate before Buffer's lenient decode - identity-challenge: web-safe base64url (no Buffer) — portable if ever client-bundled - use-conditional-passkey: capacity watermark comment (next mode ⇒ split) - specs: kill-switch cases (action + loader), audit assertions, beginDiscovery single-flight (delayed-reply harness knob)
Contributor
🧪 Test Summary
|
Works around the Zitadel constraint that a WebAuthn challenge requires an already-identified user with two complementary entry points: - Returning browsers: a signed passkey-hint cookie (loginName of the last authenticated user, written on every login success, cleared on logout) lets the /login loader mint a user-bound challenge and arm a conditional ceremony — one tap, zero typing. - Fresh browsers: the Passkey button runs a modal discovery ceremony over a self-minted challenge; the assertion's userHandle (== Zitadel userId) resolves the user via POST /login/passkey-discover, which mints the real challenge for the standard /login/passkey verify. Every user-dependent discover failure is one opaque 400 (enumeration parity); the endpoint shares the webauthn verify rate limit. - Ambient arming is hinted-only — no auto-prompt on fresh loads (password managers escalate conditional requests into full pickers); discovery is button-initiated, with failures surfaced through WebAuthnReasonCopy and the identifier field as fallback. - Sole-passkey identifiers keep REDIRECTING to /login/passkey (Task-12 product ruling) — supersedes the inline ceremony #107 shipped; its reachability + view logic (showIdentifierForm/showContinue) survive. - Chooser buttons rebranded to short labels (Email / Phone / Username). - v0.1.0 release prep (CONTRIBUTING/SECURITY, untrack .claude/settings).
- Hint lifecycle (write on every success path, clear on logout, add=1 suppression), loader arming + suppression lists (hinted and discovery), discover action matrix (userHandle resolution, opaque-400 parity, crafted-POST live-session guard), hook modes incl. beginDiscovery, button binding, #107 view-logic grafts - E2E: hinted one-tap return journey; fresh-browser button discovery with hint self-upgrade; harness passkeyDiscoverAction dispatch + JSON body capture
… hardening MEDIUM: - passkey-discover: reorder getUser → live-session guard → listAuthMethods (the local cookie guard now trips BEFORE the second provider round-trip) - passkey_discover audit events: success (hashed actor) + per-reason failures (disabled/invalid_input/no_user_handle/unresolved_user/live_session/ no_passkey_method/mint_failed/challenge_failed) — enumeration parity stays caller-facing only - AUTH_PASSKEY_DISCOVERY_ENABLED kill switch (env, default ON; 'false'/'0' disables) gating the loader arm + the discover action; documented in .env.example LOW: - PasskeyDiscoverData moved to a shared types-only module (route + hook) - decodeUserHandle: strict base64url charset gate before Buffer's lenient decode - identity-challenge: web-safe base64url (no Buffer) — portable if ever client-bundled - use-conditional-passkey: capacity watermark comment (next mode ⇒ split) - specs: kill-switch cases (action + loader), audit assertions, beginDiscovery single-flight (delayed-reply harness knob)
yahyafakhroji
force-pushed
the
feat/usernameless-passkey-login
branch
from
July 31, 2026 04:05
129385b to
a7930f5
Compare
- Comments across the branch now describe behavior in plain terms — internal process references (spec sections, decision numbers, task/phase tags, review pointers, upstream issue links) removed - .claude/reviews/pr-108-review.md untracked (local review artifact, committed by accident)
Contributor
🧪 Test Summary
|
Contributor
🧪 Test Summary
|
gaghan430
approved these changes
Jul 31, 2026
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Passkey sign-in without typing an email. Zitadel only issues WebAuthn challenges for an already-identified user, so both entry points resolve identity first, then run the standard ceremony:
passkey-hintcookie pre-arms a conditional ceremony: one tap, zero typing.userHandleresolves the user via the newPOST /login/passkey-discover, then the normal/login/passkeyverify completes sign-in.Notes
passkey_discoveraudit event.AUTH_PASSKEY_DISCOVERY_ENABLEDkill switch (default ON)./login/passkey(supersedes fix(login): make passkey sign-in reachable when password is disabled #107's inline ceremony; its view logic survives).Email).Test plan