[low priority] Passkey (WebAuthn) authentication for the dashboard, alongside master-key login #24

Open
opened 2026-08-11 10:05:52 +00:00 by james.oates · 0 comments
Owner

Summary

Add passkey (WebAuthn/FIDO2) login to the admin dashboard as step 2 of the dashboard-auth work started in #22. This is additive: the master-API-key login stays, and passkeys become a stronger/more convenient way to establish the same session.

Context

#22 replaced the browser-stored master key with a server-issued, httpOnly session cookie (SessionStore, POST /api/session login → cookie, auth_middleware accepts the cookie). Passkeys slot in cleanly on top of that: a successful WebAuthn assertion just mints the same er_session cookie — no change to how the rest of the app authenticates. #23 (output escaping + CSP) is already done.

Model / decisions from discussion

  • Additive, not a replacement. Master key remains (bootstrap + recovery + automation). Passkey is an alternative path to a session.
  • Registration requires an existing authenticated session (log in with the master key once, then register a passkey) — no unauthenticated credential enrolment.
  • Second factor, not identity you can enumerate: a passkey is RP-ID-bound and non-correlatable across sites; the RP can't derive who the user is from the credential — we bind it to the admin, not learn identity from it.
  • RP ID = the admin host (e.g. router.example.com, router.localhost in devinfra). Credentials are domain-bound, so multi-admin-host setups need per-host handling — worth noting since ER supports multiple [[admin]] hosts.
  • Store credentials (credential ID + public key + sign count) server-side. New DB-backed resource → new numbered migration + db.rs registration per the migration rules in CLAUDE.md.

Scope

  • Registration endpoint(s): WebAuthn create-options + verify, session-gated.
  • Authentication endpoint(s): assertion-options + verify → issue er_session on success.
  • Dashboard UI: "Register a passkey" (post-login) and "Sign in with a passkey" on the login gate.
  • Persistence: migration for a webauthn_credentials table; CRUD in db.rs.
  • Consider a mature Rust WebAuthn crate rather than hand-rolling (don't reinvent the wheel).

Dependencies / ordering

  • Builds on #22 (done).
  • Sensible to land after #18 (hash API keys at rest) so credential/secret-at-rest handling is consistent across the auth surface.

Priority

Low — the dashboard is already session-authenticated and XSS/CSP-hardened; passkeys are a UX + second-factor improvement, not a security gap. Tracked as the deferred "step 2" from #22.

## Summary Add passkey (WebAuthn/FIDO2) login to the admin dashboard as **step 2** of the dashboard-auth work started in #22. This is additive: the master-API-key login stays, and passkeys become a stronger/more convenient way to establish the same session. ## Context #22 replaced the browser-stored master key with a server-issued, httpOnly session cookie (`SessionStore`, `POST /api/session` login → cookie, `auth_middleware` accepts the cookie). Passkeys slot in cleanly on top of that: **a successful WebAuthn assertion just mints the same `er_session` cookie** — no change to how the rest of the app authenticates. #23 (output escaping + CSP) is already done. ## Model / decisions from discussion - **Additive, not a replacement.** Master key remains (bootstrap + recovery + automation). Passkey is an alternative path to a session. - **Registration requires an existing authenticated session** (log in with the master key once, then register a passkey) — no unauthenticated credential enrolment. - **Second factor, not identity you can enumerate:** a passkey is RP-ID-bound and non-correlatable across sites; the RP can't derive who the user is from the credential — we bind it to the admin, not learn identity from it. - **RP ID = the admin host** (e.g. `router.example.com`, `router.localhost` in devinfra). Credentials are domain-bound, so multi-admin-host setups need per-host handling — worth noting since ER supports multiple `[[admin]]` hosts. - Store credentials (credential ID + public key + sign count) server-side. New DB-backed resource → **new numbered migration + `db.rs` registration** per the migration rules in CLAUDE.md. ## Scope - Registration endpoint(s): WebAuthn create-options + verify, session-gated. - Authentication endpoint(s): assertion-options + verify → issue `er_session` on success. - Dashboard UI: "Register a passkey" (post-login) and "Sign in with a passkey" on the login gate. - Persistence: migration for a `webauthn_credentials` table; CRUD in `db.rs`. - Consider a mature Rust WebAuthn crate rather than hand-rolling (don't reinvent the wheel). ## Dependencies / ordering - Builds on #22 (done). - Sensible to land **after #18** (hash API keys at rest) so credential/secret-at-rest handling is consistent across the auth surface. ## Priority Low — the dashboard is already session-authenticated and XSS/CSP-hardened; passkeys are a UX + second-factor improvement, not a security gap. Tracked as the deferred "step 2" from #22.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#24
No description provided.