Login + sessions + logout (30min idle, 7d max) #6

Open
opened 2026-05-14 23:17:15 +02:00 by arne · 0 comments
Owner

Parent

#2

What to build

Returning users authenticate at /login with their passkey. On a successful WebAuthn assertion with the PRF extension, the backend derives the AES key, decrypts the stored ciphertext, creates an in-RAM session keyed by a cryptographically-random opaque sessionID, sets the sessionID as an HttpOnly; Secure; SameSite=Lax cookie, and redirects. An auth middleware protects every route except /login, /register, /static/*, /healthz. Idle timeout (30min) and absolute cap (7d) evict sessions. Explicit logout clears server state and the cookie. Server restart wipes RAM — users must re-authenticate.

Acceptance criteria

  • GET /login returns the login page (passkey button only — no fields)
  • Browser invokes navigator.credentials.get() with PRF extension
  • On valid assertion, backend derives PRF key, decrypts the user's token, creates session, sets cookie, 302 to /
  • On failure, returns 401 with a generic error (no leak of whether credential exists)
  • Cookie: HttpOnly; Secure; SameSite=Lax; Path=/; value is base64url of 32 random bytes
  • Session entry holds {userID, plaintextToken, serverURL, createdAt, lastActivity} in a concurrent-safe in-memory map
  • Hitting a protected route refreshes lastActivity
  • Sessions idle > 30min are evicted
  • Sessions older than 7d (regardless of activity) are evicted
  • POST /logout clears server-side session and unsets cookie
  • Server restart wipes all sessions; user re-authenticates on next request
  • Unit tests in internal/auth: session map race-free under concurrent reads/writes; idle eviction works; absolute-cap eviction works; sessionID generation is unique under load

Blocked by

## Parent #2 ## What to build Returning users authenticate at `/login` with their passkey. On a successful WebAuthn assertion with the PRF extension, the backend derives the AES key, decrypts the stored ciphertext, creates an in-RAM session keyed by a cryptographically-random opaque sessionID, sets the sessionID as an `HttpOnly; Secure; SameSite=Lax` cookie, and redirects. An auth middleware protects every route except `/login`, `/register`, `/static/*`, `/healthz`. Idle timeout (30min) and absolute cap (7d) evict sessions. Explicit logout clears server state and the cookie. Server restart wipes RAM — users must re-authenticate. ## Acceptance criteria - [ ] GET `/login` returns the login page (passkey button only — no fields) - [ ] Browser invokes `navigator.credentials.get()` with PRF extension - [ ] On valid assertion, backend derives PRF key, decrypts the user's token, creates session, sets cookie, 302 to `/` - [ ] On failure, returns 401 with a generic error (no leak of whether credential exists) - [ ] Cookie: `HttpOnly; Secure; SameSite=Lax; Path=/`; value is base64url of 32 random bytes - [ ] Session entry holds `{userID, plaintextToken, serverURL, createdAt, lastActivity}` in a concurrent-safe in-memory map - [ ] Hitting a protected route refreshes `lastActivity` - [ ] Sessions idle > 30min are evicted - [ ] Sessions older than 7d (regardless of activity) are evicted - [ ] POST `/logout` clears server-side session and unsets cookie - [ ] Server restart wipes all sessions; user re-authenticates on next request - [ ] Unit tests in `internal/auth`: session map race-free under concurrent reads/writes; idle eviction works; absolute-cap eviction works; sessionID generation is unique under load ## Blocked by - #5
Sign in to join this conversation.
No milestone
No project
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
posta/chat#6
No description provided.