Files
headquarter/openspec/changes/archive/2026-05-17-auth-oauth/design.md
T

4.1 KiB

Context

The existing repository now includes core database infrastructure and user models, but authentication is not implemented yet. The target behavior is defined by openspec/specs/auth-oauth/spec.md and refined through approved decisions: Authentik OIDC as the identity provider, strict production cookies, internal JWT access tokens, and server-side refresh token storage with revocation.

The backend is Python-based with async SQLAlchemy and Alembic. This change must integrate with that stack while keeping trust boundaries explicit and supporting predictable local development.

Goals / Non-Goals

Goals:

  • Implement Authentik OAuth2/OIDC login and callback flow.
  • Validate provider tokens via JWKS before creating local session credentials.
  • Mint internal short-lived JWT access tokens for API authorization.
  • Persist hashed opaque refresh tokens in DB with rotation and revocation.
  • Provide explicit logout that invalidates refresh state and clears cookies.
  • Enforce environment-aware cookie policy (strict in production, relaxed on localhost).

Non-Goals:

  • RBAC policy engine and permission modeling.
  • Multi-device session management UX.
  • Additional social identity providers.

Decisions

  1. Use OIDC code flow + Authentik JWKS validation before local minting

    • Rationale: prevents blind trust in token exchange payloads and centralizes signature/claim checks (iss, aud, exp).
    • Alternative considered: trust exchange response without independent validation. Rejected due to weaker security posture.
  2. Issue internal JWT access tokens instead of forwarding provider access tokens

    • Rationale: stable internal contract, decoupled claim shape, simpler downstream authorization.
    • Alternative considered: pass-through provider tokens. Rejected due to tighter coupling and reduced control over TTL/claims.
  3. Use DB-backed opaque refresh tokens with hash-at-rest + rotation

    • Rationale: supports immediate revocation on logout and tighter reuse detection.
    • Alternative considered: stateless long-lived JWT refresh tokens. Rejected because revocation and replay handling are weaker.
  4. Environment-aware cookie policy with secure defaults

    • Production: Secure=true, SameSite=strict, HttpOnly=true.
    • Local dev: Secure=false, SameSite=lax, HttpOnly=true.
    • Rationale: preserves security in production while enabling localhost development without TLS.
  5. Add dedicated refresh token persistence model and migration

    • Table fields: id, user_id, token_hash, expires_at, created_at, revoked_at, user_agent, ip_address.
    • Indexes: unique token_hash, plus user_id and expires_at indexes.

Risks / Trade-offs

  • [JWKS endpoint/network failures] -> Cache JWKS keys with bounded TTL and fail closed with 401 on unverifiable tokens.
  • [Clock skew affecting token validity] -> Allow small validation leeway and keep server clock synchronized.
  • [Refresh token replay attempts] -> Rotate per refresh, revoke reused chains, and force re-authentication.
  • [Cookie behavior differences across browsers/environments] -> Centralize cookie option builder and test dev/prod permutations.
  • [Added implementation surface area] -> Keep modules focused (provider client, JWT service, refresh store, routes) and maintain high test coverage.

Migration Plan

  1. Add new refresh token model and Alembic migration.
  2. Add configuration for OIDC endpoints/client credentials/JWT secret/cookie mode.
  3. Implement auth services (provider exchange + JWKS verification, JWT mint/verify, refresh store).
  4. Implement routes: /auth/login, /auth/callback, /auth/refresh, /auth/logout, /auth/me.
  5. Add/expand tests (unit + integration) for auth and token lifecycle.
  6. Verify quality gates (pytest, ruff, mypy) and migration status.

Rollback:

  • Revert route/service changes and run Alembic downgrade for refresh-token migration if deployment requires rollback.

Open Questions

  • None blocking for initial implementation.
  • Optional follow-up: enforce single active refresh token per user-agent/device (currently out of scope).