# Auth OAuth Design ## Goal Implement OAuth2/OIDC authentication via Authentik with internal JWT access tokens, DB-backed refresh token rotation, secure cookie handling, and explicit logout revocation. ## Scope In scope: - Authentik code flow callback handling - Authentik token verification via JWKS - Internal JWT minting and validation - Refresh token persistence and rotation - Logout revocation and cookie clearing - Cookie policy split for dev vs production Out of scope: - RBAC policy engine - Multi-device session management UI - Social providers beyond existing Authentik setup ## Chosen Approach Use full OIDC callback exchange with JWKS verification, then mint an internal short-lived JWT and store opaque refresh tokens server-side. Why this approach: - Keeps trust boundary explicit (no blind trust of exchange payload) - Allows immediate refresh-token revocation on logout - Decouples internal auth contract from external provider claim shape ## Defaults - Access token TTL: 15 minutes - Refresh token TTL: 7 days - Cookie mode: - Production: `Secure=true`, `SameSite=strict`, `httpOnly=true` - Local development: `Secure=false`, `SameSite=lax`, `httpOnly=true` ## Architecture 1. Frontend calls `GET /auth/login`. 2. Backend redirects to Authentik authorize endpoint. 3. Authentik redirects to backend callback with `code`. 4. Backend exchanges `code` for Authentik tokens. 5. Backend validates Authentik access token using Authentik JWKS (`iss`, `aud`, `exp`, signature). 6. Backend maps claims to local user record (create/update by `authentik_id`). 7. Backend mints internal access JWT and opaque refresh token. 8. Backend stores hashed refresh token in database and sets cookies. 9. Protected endpoints validate internal access JWT. 10. `POST /auth/refresh` rotates refresh token and issues new access JWT. 11. `POST /auth/logout` revokes refresh token and clears cookies. ## Data Model Add a `refresh_tokens` table: - `id`: UUID primary key - `user_id`: UUID foreign key -> `users.id` - `token_hash`: string (hash of opaque refresh token; never store raw token) - `expires_at`: timestamp with timezone - `created_at`: timestamp with timezone - `revoked_at`: timestamp with timezone, nullable - `user_agent`: string, nullable - `ip_address`: string, nullable Indexes: - unique index on `token_hash` - index on `user_id` - index on `expires_at` ## API Endpoints ### `GET /auth/login` - Redirects to Authentik authorize URL with state and nonce. ### `GET /auth/callback` - Validates state. - Exchanges code at Authentik token endpoint. - Verifies Authentik access token via JWKS. - Upserts local user. - Mints internal access JWT + opaque refresh token. - Persists hashed refresh token record. - Sets `access_token` and `refresh_token` cookies. ### `POST /auth/refresh` - Reads `refresh_token` cookie. - Hashes and finds matching non-revoked, non-expired DB row. - If valid, revokes old row and creates a new row (rotation). - Mints new internal access JWT and new opaque refresh token. - Sets rotated cookies. ### `POST /auth/logout` - Reads refresh cookie if present. - Revokes corresponding DB token row. - Clears access and refresh cookies. ### `GET /auth/me` - Validates internal access JWT. - Returns current user payload. ## Token Strategy ### Internal access JWT Claims: - `sub`: local user id - `email` - `name` - `roles` (optional, if available) - `iat`, `exp` Signing: - Use configured backend signing secret/algorithm. ### Refresh token - Opaque, random, high-entropy value - Hashed before persistence - Rotated on each refresh - Revoked on logout and on detected reuse ## Security Rules - Never expose token contents to frontend JS (httpOnly cookies only). - Validate Authentik token signature and critical claims before minting local JWT. - Enforce strict cookie attributes by environment. - Log security-relevant events with safe redaction. - Return generic auth errors to clients; keep details in server logs. ## Error Handling - Invalid code exchange -> `401` - JWKS verification failure -> `401` - Missing/invalid refresh cookie -> `401` - Revoked/expired refresh token -> `401` - Reuse detection (if token already rotated/revoked) -> revoke chain and force login Error body shape: - stable machine-readable code - non-sensitive message ## Testing Strategy Unit tests: - cookie option builder (dev vs prod) - Authentik token verification helper - internal JWT mint/verify helpers - refresh hash + rotation logic Integration tests: - callback creates/updates user and sets cookies - refresh rotates token and invalidates previous token - logout revokes token and clears cookies - protected endpoint rejects invalid/expired JWT Quality gates: - `pytest` passes - `mypy .` passes - `ruff check .` passes ## Implementation Notes - Keep auth logic in focused modules (provider client, jwt service, refresh store, route handlers). - Keep DB writes idempotent where feasible (user upsert path). - Keep changes scoped to auth-oauth and required schema support.