ASVS L3 Web Session Implementation Plan
Status: Draft
Purpose
Implementation plan for ADR 0012 (ASVS L3 cookie sessions). This document is the execution guide and acceptance checklist. The ADR remains the decision record.
ADR reference: docs/architecture/ADR/0012-asvs-l3-cookie-sessions.md
Scope
- Web app (same-site) authentication uses only cookie sessions.
- API clients and embeds are out of scope for this plan.
- Capabilities and RLS remain server-resolved and unchanged.
Non-goals
- Bearer tokens for the web app.
- Cross-site embedding and third-party cookie support.
- Global i18n error contract beyond auth/CSRF error codes.
Defaults and Contracts (from ADR)
- Cookie name:
__Host-evalium_session - Cookie attrs:
HttpOnly; Secure; SameSite=Lax; Path=/(no Domain) - Token: 32+ bytes CSPRNG, base64url, never in URL/query, never logged
- Token hash:
HMAC-SHA-256(secret, token); constant-time compare - CSRF: synchronizer token, session-bound,
X-CSRF-Tokenrequired - Origin/Referer validation: Origin if present, else strict same-origin Referer
- Fetch Metadata: enforce
Sec-Fetch-Site,Sec-Fetch-Mode,Sec-Fetch-Dest - Content-Type allowlist for unsafe methods (default
application/json) - Cache-Control:
no-storeon any response that sets/uses session or CSRF - HSTS required for web origin
- No credentialed CORS for session-auth endpoints
Lifecycle defaults
- Idle timeout: 15 minutes
- Absolute lifetime: 12 hours
- Renewal cadence: 4 hours
- Rotation overlap: 5 minutes
- last_seen write throttle: 5 minutes
Implementation Steps
1) Data model and migrations
Create session storage in Postgres.
Table: auth_sessions
Required columns:
iduuid PK (internal identifier)token_hashbytea UNIQUE (HMAC-SHA-256)csrf_hashbytea (HMAC-SHA-256)tenant_id,org_unit_id,user_id,scope_allrole_idsuuid[]created_at,last_seen_atidle_expires_at,absolute_expires_atrevoked_at,revoked_reason,revoked_byuser_agent(optional, audit only)ip(optional, audit only)device_fingerprint(optional, audit only)
Indexes:
token_hashunique(idle_expires_at),(absolute_expires_at),(revoked_at)(tenant_id, user_id)
Add a cleanup job to purge expired/revoked sessions after retention window.
2) Service layer
Add session store + helpers:
- Create session (token + csrf, hashed, expiries)
- Validate session (revoked, idle, absolute)
- Rotate token (with overlap window) and rotate CSRF
- Revoke session(s) by user/tenant
- Throttle
last_seen_atupdates
Represent rotation overlap:
- Store
token_hash_prev+prev_valid_untilor a separate table of valid hashes.
3) Middleware
Web UI routes must be in a dedicated router group. In this group:
- Reject
Authorizationheader withAUTH_HEADER_NOT_ALLOWED - Read cookie
__Host-evalium_session - Lookup session by
token_hash - Enforce expiry/revocation
- Attach
sessionIdto context for logging - Build
AuthContextand callpg.WithScope
4) Auth endpoints
-
POST /auth/verify- Verify magic link token
- Create session + set cookie
- Return minimal JSON (expiresAt)
Cache-Control: no-store
-
GET /auth/me- Returns identity + scope + capabilities + CSRF token
Cache-Control: no-store
-
GET /auth/csrf(optional)- Returns CSRF token
Cache-Control: no-store
-
POST /auth/logout- Revoke current session
- Clear cookie
- CSRF enforced
-
GET /auth/sessions- List active sessions for user (redacted)
-
POST /auth/sessions/revoke -
POST /auth/sessions/revoke-all- CSRF enforced
5) CSRF enforcement
For all unsafe methods (POST, PUT, PATCH, DELETE) in web UI routes:
- Require
X-CSRF-Token - Validate Origin or Referer
- Enforce Fetch Metadata rules
- Require Content-Type in allowlist
- Do not allow credentialed CORS
6) Magic link safety
- Magic link verify token may appear in URL (not session token)
- Never log magic link tokens
- Enforce
Cache-Control: no-storeandReferrer-Policy: no-referrer - Do not load third-party assets on verify route
7) Error contract
Auth/CSRF errors must return stable codes:
AUTH_UNAUTHENTICATEDAUTH_SESSION_EXPIREDAUTH_CSRF_MISSINGAUTH_CSRF_ORIGIN_INVALIDAUTH_HEADER_NOT_ALLOWED
Response shape:
{
"code": "AUTH_CSRF_MISSING",
"message": "optional human text",
"requestId": "optional request id"
}
8) Rate limiting
/auth/login(magic link issuance): per IP + per identifier/auth/verify: per IP + per token- Ensure enumeration-safe responses
9) Observability
- Log
requestId+sessionIdfor auth/CSRF failures - Never log session tokens or CSRF tokens
- Add metrics for session create/rotate/revoke
Testing Plan
Unit tests:
- Token generation length + base64url
- Constant-time compare usage
- CSRF token validation
- Session expiry logic (idle + absolute)
- Rotation overlap validity
Integration tests:
POST /auth/verifysets cookie + no-store/auth/mereturns CSRF and capabilities- CSRF enforcement on unsafe methods
- Origin/Referer validation
- Authorization header rejection on web routes
- Logout revokes session and clears cookie
Security tests:
- CSRF rejection without header
- Origin mismatch rejection
- Use of
Cache-Control: no-storeon session responses
Acceptance Checklist
- Migration applied and sqlc generated
- Middleware uses cookie sessions only on web routes
- Authorization header rejected on web routes
- CSRF enforced on all unsafe methods
- Origin/Referer and Fetch Metadata checks active
- Session lifecycle enforced (idle/absolute/renewal/overlap)
- Logout revokes session and clears cookie
- Rate limits for auth endpoints
- No-store headers on session/identity responses
- Tests green in CI