# 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 `POST /api/admin/auth/refresh` (with `credentials: 'include'`) chained with `GET /api/admin/users/me` on mount — same wire shape as the 401-retry path in `api/client.ts`. | | `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** (consolidated by AZ-510): 1. Mount → set `loading: true`. 2. `fetch(getApiBase() + endpoints.admin.authRefresh(), { method: 'POST', credentials: 'include' })` to ask the server "do I have a valid session?". Direct `fetch` (not `api.post`) because `api.post` does not thread `credentials: 'include'` and widening it would change CORS posture for every authed callsite. 3. On 200 → `setToken(data.token)`, then `api.get(endpoints.admin.usersMe())` to fetch the user shape (the POST refresh response is `{ token }` only — no user payload). On `/users/me` 200 → `setUser(authUser)`, `loading: false`. On `/users/me` failure → `setToken(null)`, `setUser(null)`, `loading: false`, `console.error` carries the diagnostic (refresh OK / user GET failed). 4. On refresh 4xx or network failure → `setUser(null)`, `loading: false`. `ProtectedRoute` then redirects to `/login`. 5. **StrictMode**: a module-scoped in-flight promise deduplicates the bootstrap network round-trip across React 18+ StrictMode double-mounts so the backend cookie rotation does not race itself. Bootstrap and the 401-retry path in `api/client.ts:88` now share a single wire shape — `POST /api/admin/auth/refresh` with credentials. Finding **B3** (bootstrap missing `credentials: 'include'`) is closed. **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'`**~~ — closed by AZ-510. Bootstrap now uses POST refresh + chained `/users/me` with credentials, matching the 401-retry path. - **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` |