# 02 — Auth ## 1. High-Level Overview **Purpose**: Authentication state, login/logout/refresh plumbing, and the `` gate that wraps every non-public route. **Architectural Pattern**: React Context provider + route-guard component. **Upstream dependencies**: `00_foundation` (types), `01_api-transport`. **Downstream consumers**: `04_login`, `10_app-shell`, every authenticated page (indirectly via the route gate). ## 2. Internal Interfaces ### `src/auth/AuthContext.tsx` | Export | Signature | Notes | |--------|-----------|-------| | `AuthProvider({ children })` | React component | Wraps the app below `BrowserRouter`. Bootstraps via `GET /api/admin/auth/refresh` on mount. | | `useAuth(): AuthContextValue` | hook | Read-only access to `{ user, permissions, login, logout, refresh, loading }`. | **`AuthContextValue`** (output DTO): ``` user: User | null permissions: Permission[] ← from server, used by route guards & UI loading: boolean ← true during initial bootstrap and active refresh login(c): Promise ← POST /api/admin/auth/login logout(): Promise ← POST /api/admin/auth/logout refresh(): Promise ← POST /api/admin/auth/refresh ``` ### `src/auth/ProtectedRoute.tsx` | Export | Signature | Notes | |--------|-----------|-------| | `ProtectedRoute({ children, requirePermission? })` | React component | Renders children if authenticated; otherwise navigates to `/login`. Optional `requirePermission` is checked against `useAuth().permissions` and renders a 403 placeholder on miss. | ## 3. External API Specification Consumes only — does not expose. Endpoint set (from `_docs/02_document/modules/src__auth__AuthContext.md`): | Method | Path | Auth | When | |--------|------|------|------| | POST | `/api/admin/auth/login` | public | Login form submit | | POST | `/api/admin/auth/refresh` | cookie | Bootstrap + on 401 retry | | POST | `/api/admin/auth/logout` | cookie | Header → Logout | | GET | `/api/admin/auth/me` | cookie | (post-login profile fetch, if implemented) | ## 5. Implementation Details **State Management**: Single React context. Token lives in an HTTP-only cookie (server-managed); the React state holds only the parsed user + permissions. No `localStorage`. **Bootstrap sequence**: 1. Mount → set `loading: true`. 2. `api.post('/api/admin/auth/refresh')` to ask the server "do I have a valid session?". 3. On 200 → store user + permissions, `loading: false`. 4. On 4xx → user stays `null`, `loading: false`. `ProtectedRoute` then redirects. > **PRIORITY finding (B3, copied from state.json)**: the bootstrap call inside `AuthContext.tsx` does not pass `credentials: 'include'` consistently — the cookie is therefore not sent on the very first request and bootstrap silently fails on a fresh page load. Confirmed real bug; Step 4 fix. **Spinner UX**: `ProtectedRoute` renders a centered spinner during `loading`. The spinner has **no** `role="status"` / no accessible label / no timeout. (Findings B4, joint with Step 4 client.ts timeout flag.) ## 7. Caveats & Edge Cases - **Bootstrap missing `credentials: 'include'`** → users land on `/login` even with a valid cookie session. PRIORITY Step 4. - **Spinner accessibility** — Step 4. - **Token-rotation interaction with SSE** — see `01_api-transport`. Auth refresh works for fetch but breaks every active EventSource. - **No idle-timeout / inactivity logout** — server-side concern; UI tolerates whatever the server enforces. ## 8. Dependency Graph **Must be implemented after**: `00_foundation`, `01_api-transport`. **Can be implemented in parallel with**: nothing inside this workspace's gate path. **Blocks**: `03_shared-ui` (Header reads `useAuth`), `04_login`, `10_app-shell`, indirectly every authenticated page. ## Module Inventory | Path | Module Doc | |------|------------| | `src/auth/AuthContext.tsx` | `_docs/02_document/modules/src__auth__AuthContext.md` | | `src/auth/ProtectedRoute.tsx` | `_docs/02_document/modules/src__auth__ProtectedRoute.md` |