add uncommitted plans and CLAUDE.md
This commit is contained in:
parent
6b4cbdc152
commit
fb133f9cba
10 changed files with 5241 additions and 0 deletions
88
docs/plans/2026-02-16-discoverable-credentials-design.md
Normal file
88
docs/plans/2026-02-16-discoverable-credentials-design.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Discoverable Credentials (Usernameless WebAuthn)
|
||||
|
||||
## Problem
|
||||
|
||||
The current WebAuthn login flow is username-first: the user types their username,
|
||||
the server looks up their credential IDs, and sends those as `allowCredentials`
|
||||
to the browser. This requires the user to remember and type their username.
|
||||
|
||||
Additionally, the current WebAuthn login flow has bugs that make it non-functional:
|
||||
the login button isn't wired up in JS, the JS isn't loaded on the login page, and
|
||||
the server returns HX-Redirect headers but the JS tries to parse JSON.
|
||||
|
||||
## Decision
|
||||
|
||||
Switch to discoverable credentials (passkeys). The browser/OS credential picker
|
||||
handles identity selection — no username needed.
|
||||
|
||||
Key choices:
|
||||
- **Usernameless only** — remove the username field from WebAuthn login entirely
|
||||
- **User verification: preferred** — ask for PIN/biometric but allow authenticators
|
||||
that only support presence (touch)
|
||||
- **Resident key: required** — credentials must be stored on the authenticator as
|
||||
discoverable credentials
|
||||
- **JSON response** — the WebAuthn complete endpoint returns
|
||||
`{"redirect": "..."}` since the flow uses fetch(), not HTMX
|
||||
|
||||
## Registration changes
|
||||
|
||||
`WebAuthnService.begin_registration()` passes
|
||||
`resident_key_requirement=ResidentKeyRequirement.REQUIRED` and
|
||||
`user_verification=UserVerificationRequirement.PREFERRED` to
|
||||
`Fido2Server.register_begin()`. This tells the authenticator to store a resident
|
||||
credential with the user's ID embedded.
|
||||
|
||||
No schema changes needed. The existing `webauthn_credentials` table and model
|
||||
already store everything required.
|
||||
|
||||
## Authentication changes
|
||||
|
||||
### Begin (`POST /login/webauthn/begin`)
|
||||
|
||||
- No username parameter (change from POST to GET since there's no form body)
|
||||
- Call `begin_authentication(credentials=None, user_verification=PREFERRED)` —
|
||||
empty `allowCredentials` triggers the browser's passkey picker
|
||||
- Store only `webauthn_login_state` in session (no userid — we don't know it yet)
|
||||
|
||||
### Complete (`POST /login/webauthn/complete`)
|
||||
|
||||
- Extract `userHandle` from the assertion response — this contains the `user_id`
|
||||
bytes set during registration (`userid.encode()`)
|
||||
- Decode `userHandle` to get the userid string
|
||||
- Look up the user's stored credentials by userid
|
||||
- Verify the assertion against those credentials
|
||||
- Update sign count
|
||||
- Set session
|
||||
- Return `JSONResponse({"redirect": "/manage/credentials"})` (or the OIDC
|
||||
redirect target if a pending authorization exists)
|
||||
|
||||
### Service layer
|
||||
|
||||
Add `user_verification` parameter to `begin_registration()` and
|
||||
`begin_authentication()` in `WebAuthnService`, passing them through to the
|
||||
fido2 server methods.
|
||||
|
||||
## Frontend changes
|
||||
|
||||
### `webauthn.js`
|
||||
|
||||
- `beginAuthentication()` drops the username parameter and FormData body
|
||||
- Change fetch to GET for `/login/webauthn/begin`
|
||||
- On success, read `data.redirect` from JSON response (server now returns JSON)
|
||||
- Wire `#webauthn-login-btn` in the `DOMContentLoaded` handler
|
||||
|
||||
### `login.html`
|
||||
|
||||
- Remove the username field and label from the WebAuthn section
|
||||
- Add `{% block scripts %}` to load `webauthn.js`
|
||||
|
||||
## Files changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/porchlight/authn/webauthn.py` | Add `resident_key_requirement` and `user_verification` params |
|
||||
| `src/porchlight/authn/routes.py` | Rewrite begin (no username, GET), fix complete (userHandle lookup, JSON response) |
|
||||
| `src/porchlight/static/webauthn.js` | Drop username, wire login button, fix response handling |
|
||||
| `src/porchlight/templates/login.html` | Remove username field, add scripts block |
|
||||
|
||||
No database migrations. No model changes. No new repository methods.
|
||||
Loading…
Add table
Add a link
Reference in a new issue