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
-
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.
- Rationale: prevents blind trust in token exchange payloads and centralizes signature/claim checks (
-
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.
-
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.
-
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.
- Production:
-
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, plususer_idandexpires_atindexes.
- Table fields:
Risks / Trade-offs
- [JWKS endpoint/network failures] -> Cache JWKS keys with bounded TTL and fail closed with
401on 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
- Add new refresh token model and Alembic migration.
- Add configuration for OIDC endpoints/client credentials/JWT secret/cookie mode.
- Implement auth services (provider exchange + JWKS verification, JWT mint/verify, refresh store).
- Implement routes:
/auth/login,/auth/callback,/auth/refresh,/auth/logout,/auth/me. - Add/expand tests (unit + integration) for auth and token lifecycle.
- 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).