Skip to main content

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-Token required
  • 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-store on 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:

  • id uuid PK (internal identifier)
  • token_hash bytea UNIQUE (HMAC-SHA-256)
  • csrf_hash bytea (HMAC-SHA-256)
  • tenant_id, org_unit_id, user_id, scope_all
  • role_ids uuid[]
  • created_at, last_seen_at
  • idle_expires_at, absolute_expires_at
  • revoked_at, revoked_reason, revoked_by
  • user_agent (optional, audit only)
  • ip (optional, audit only)
  • device_fingerprint (optional, audit only)

Indexes:

  • token_hash unique
  • (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_at updates

Represent rotation overlap:

  • Store token_hash_prev + prev_valid_until or a separate table of valid hashes.

3) Middleware

Web UI routes must be in a dedicated router group. In this group:

  • Reject Authorization header with AUTH_HEADER_NOT_ALLOWED
  • Read cookie __Host-evalium_session
  • Lookup session by token_hash
  • Enforce expiry/revocation
  • Attach sessionId to context for logging
  • Build AuthContext and call pg.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
  • Magic link verify token may appear in URL (not session token)
  • Never log magic link tokens
  • Enforce Cache-Control: no-store and Referrer-Policy: no-referrer
  • Do not load third-party assets on verify route

7) Error contract

Auth/CSRF errors must return stable codes:

  • AUTH_UNAUTHENTICATED
  • AUTH_SESSION_EXPIRED
  • AUTH_CSRF_MISSING
  • AUTH_CSRF_ORIGIN_INVALID
  • AUTH_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 + sessionId for 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/verify sets cookie + no-store
  • /auth/me returns 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-store on 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