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?
#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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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_sessioncookie, which is the highest-risk change in #31. It also brings:@simplewebauthn/server, plus the browser package)Environmental constraints worth knowing up front
http://localhostqualifies, so local development works; QA and production are HTTPS behind Nginx Proxy Manager, so they qualify too./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
Design questions to resolve before implementing
Broken into six issues
This is now a tracking issue for the Passkeys project.
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