Passkey (WebAuthn) login for customers #36

Closed
opened 2026-08-17 17:10:23 -05:00 by bermudalamb · 1 comment
Owner

Split out of #31 (item 6).

Goal

Customers can register a passkey and use it to sign in, alongside the existing email and password.

Why this is its own project

It introduces a second authentication path next to the current bcrypt + rd_session cookie, which is the highest-risk change in #31. It also brings:

  • A new dependency (@simplewebauthn/server, plus the browser package)
  • New tables for credentials and for in-flight challenges
  • Two distinct ceremonies — registration and authentication — each with a server-issued challenge that must be single-use and expiring
  • Environment-specific configuration

Environmental constraints worth knowing up front

  • WebAuthn requires a secure context. http://localhost qualifies, so local development works; QA and production are HTTPS behind Nginx Proxy Manager, so they qualify too.
  • The Relying Party ID is bound to a domain. Passkeys registered against QA will not work in production, and vice versa. QA can prove the flow, never the credentials.
  • Customer authentication is separate from the authentik SSO that gates /admin, so there is no interaction between the two — but note QA gates the whole site behind authentik, which sits in front of the passkey ceremony.

Depends on

  • #32 (password reset) — passkey-only login cannot ship without account recovery. A customer who loses their authenticator must have a way back in.
  • #33 (disable account) — a disabled account must not be able to authenticate via passkey either, so that rule should exist first.

Design questions to resolve before implementing

  • Passkey as an additional factor or a full alternative to the password?
  • Multiple passkeys per customer, and management/revocation UI?
  • What happens when a customer registers a passkey and later resets their password — does either invalidate the other?
  • Discoverable credentials (usernameless) or an email-first flow?
  • How is a lost authenticator handled, beyond password reset?
Split out of #31 (item 6). ## Goal Customers can register a passkey and use it to sign in, alongside the existing email and password. ## Why this is its own project It introduces a second authentication path next to the current bcrypt + `rd_session` cookie, which is the highest-risk change in #31. It also brings: - A new dependency (`@simplewebauthn/server`, plus the browser package) - New tables for credentials and for in-flight challenges - Two distinct ceremonies — registration and authentication — each with a server-issued challenge that must be single-use and expiring - Environment-specific configuration ## Environmental constraints worth knowing up front - WebAuthn requires a secure context. `http://localhost` qualifies, so local development works; QA and production are HTTPS behind Nginx Proxy Manager, so they qualify too. - The Relying Party ID is bound to a domain. **Passkeys registered against QA will not work in production**, and vice versa. QA can prove the flow, never the credentials. - Customer authentication is separate from the authentik SSO that gates `/admin`, so there is no interaction between the two — but note QA gates the *whole* site behind authentik, which sits in front of the passkey ceremony. ## Depends on - **#32 (password reset)** — passkey-only login cannot ship without account recovery. A customer who loses their authenticator must have a way back in. - **#33 (disable account)** — a disabled account must not be able to authenticate via passkey either, so that rule should exist first. ## Design questions to resolve before implementing - Passkey as an additional factor or a full alternative to the password? - Multiple passkeys per customer, and management/revocation UI? - What happens when a customer registers a passkey and later resets their password — does either invalidate the other? - Discoverable credentials (usernameless) or an email-first flow? - How is a lost authenticator handled, beyond password reset?
Author
Owner

Broken into six issues

This is now a tracking issue for the Passkeys project.

Issue Scope Depends on
#37 — Schema, dependency, per-environment RP config Groundwork; no user-visible behavior
#38 — Registration ceremony Signed-in customer enrolls a passkey #37
#39 — Authentication ceremony Signing in with a passkey #38
#40 — Manage and revoke passkeys Account page listing, add, remove #38
#41 — Login page integration and fallback Surfacing it, feature detection, unsupported browsers #39
#42 — Account recovery and password-reset interaction The safety net #32, #39

Suggested order: #37#38#40#39#41#42.

#40 comes before #39 deliberately. Once a customer can enroll a passkey they can also strand one, and shipping enrollment without any way to see or remove a credential is worse than not offering passkeys at all.

#42 is last but is the one not to drop. Every other issue adds a capability; that one is what stops a customer being locked out of a real account with real order history.

Standing constraints for the whole project

  • The Relying Party ID is domain-bound, so passkeys registered against QA never work in production. QA proves the flow, the fallbacks, and the copy — never the credentials. Production needs its own smoke test after deploy.
  • Password login remains the fallback throughout. Nothing in this project should make it possible to end up with an account that cannot be recovered.
  • A disabled account (#33) must not be able to authenticate via passkey. Enforcing that only on the password path would make passkeys a way around it.
## Broken into six issues This is now a tracking issue for the Passkeys project. | Issue | Scope | Depends on | | --- | --- | --- | | #37 — Schema, dependency, per-environment RP config | Groundwork; no user-visible behavior | — | | #38 — Registration ceremony | Signed-in customer enrolls a passkey | #37 | | #39 — Authentication ceremony | Signing in with a passkey | #38 | | #40 — Manage and revoke passkeys | Account page listing, add, remove | #38 | | #41 — Login page integration and fallback | Surfacing it, feature detection, unsupported browsers | #39 | | #42 — Account recovery and password-reset interaction | The safety net | #32, #39 | Suggested order: **#37 → #38 → #40 → #39 → #41 → #42.** #40 comes before #39 deliberately. Once a customer can enroll a passkey they can also strand one, and shipping enrollment without any way to see or remove a credential is worse than not offering passkeys at all. #42 is last but is the one not to drop. Every other issue adds a capability; that one is what stops a customer being locked out of a real account with real order history. ### Standing constraints for the whole project - The Relying Party ID is domain-bound, so passkeys registered against QA never work in production. QA proves the flow, the fallbacks, and the copy — never the credentials. Production needs its own smoke test after deploy. - Password login remains the fallback throughout. Nothing in this project should make it possible to end up with an account that cannot be recovered. - A disabled account (#33) must not be able to authenticate via passkey. Enforcing that only on the password path would make passkeys a way around it.
bermudalamb added this to the Passkey Implementation project 2026-08-17 17:21:28 -05:00
bermudalamb self-assigned this 2026-08-17 17:21:31 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#36