# 02 — Auth

## 1. High-Level Overview

**Purpose**: Authentication state, login/logout/refresh plumbing, and the `<ProtectedRoute>` 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<User>  ← POST /api/admin/auth/login
logout():        Promise<void>  ← POST /api/admin/auth/logout
refresh():       Promise<void>  ← 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` |
