# Auth ## Overview Authentication is stateless: the server issues a signed JWT on login and never stores session state. Every request carries the token in an HTTP-only cookie; the server verifies the signature and expiry on each request without any database or cache lookup. This makes horizontal scaling trivial — any replica can handle any request. Credential storage is pluggable. The initial implementation reads from a local file mounted by Kubernetes from a ConfigMap, which is sufficient for dev and early deployments. The interface is designed so the file-based backend can be swapped for an OIDC/SSO provider without touching the JWT layer or any route handler. ## Flow ``` POST /auth/login │ ├─ read userId + password from form body ├─ load credentials from backend (file or SSO) ├─ verify password (bcrypt compare) ├─ invalid → 401, re-render login form with error message └─ valid → sign JWT → set HttpOnly cookie → redirect to / Subsequent requests │ ├─ hooks.server.ts reads `session` cookie ├─ verifyToken() checks signature + expiry (no I/O) ├─ valid → populate event.locals.user → proceed └─ invalid → redirect to /auth/login GET /auth/logout │ └─ clear `session` cookie → redirect to /auth/login ``` ## JWT - **Algorithm**: HS256 with a secret from `AUTH_JWT_SECRET` env var - **TTL**: 8 hours; encoded in the `exp` claim - **Payload**: `{ username: string }` where `username` is the user's `id` field - **Transport**: `HttpOnly; SameSite=Lax; Secure; Path=/` cookie named `session` - **Verification**: pure in-memory — `jose.jwtVerify()` with no external calls ## Credential Backend ### Local File A JSON file mounted into the pod from a Kubernetes ConfigMap: ```json [ { "id": "admin", "passwordHash": "$2b$10$...", "name": "Admin User", "email": "admin@example.com", "workspaces": ["default"] } ] ``` Fields: `id` (required, used as login identifier and stored in JWT), `passwordHash` (required, bcrypt), `name` (optional, display name), `email` (optional), `workspaces` (optional, list of workspace IDs the user may access; defaults to `[]`). - Path: `AUTH_USERS_FILE` env var - Read once at server startup in `Auth.init()` and cached in memory; pod restart required to pick up changes - Passwords are bcrypt hashes (cost factor 10). Generate with: `htpasswd -nbB admin mypassword | cut -d: -f2` **ConfigMap example:** ```yaml apiVersion: v1 kind: ConfigMap metadata: name: agent-paas-users namespace: agent-paas data: users.json: | [ { "id": "admin", "passwordHash": "$2b$10$...", "workspaces": ["default"] } ] ``` Mount it as a volume at `/etc/agent-paas/users.json` in the pod spec. ### SSO / OIDC When SSO is required, replace the `Auth` class with an OIDC-backed implementation that: 1. Redirects unauthenticated users to the IdP authorization endpoint 2. Handles the callback, exchanges the code for tokens, validates the ID token 3. Issues the same internal JWT (same `{ username: id }` payload) so the rest of the system is unaffected The `Server` class wires in the concrete `Auth` implementation; no route handler or hook needs to change. ## Scaling Properties - **No shared state** — JWT verification is pure CPU; no Redis, no DB, no sticky sessions needed - **Credential cache** — the users list is loaded once at startup per replica; all replicas read the same ConfigMap at pod start, so they stay consistent without coordination - **Secret rotation** — rotating `AUTH_JWT_SECRET` invalidates all existing sessions (users must re-login); this is acceptable for a developer tool with an 8-hour TTL