localStorage. Auth state is held in React context and enforced per route.
The Axios Client
src/api/client.ts exports one shared axios instance. Every API module and page imports this default export rather than calling axios directly.
| Concern | Behavior |
|---|---|
| Base URL | import.meta.env.VITE_API_URL (set per brand) |
| Default header | Content-Type: application/json |
| Auth injection | Request interceptor reads the token from localStorage[TOKEN_KEY] and sets Authorization: Bearer <token> |
| 401 handling | Response interceptor clears the token and hard-redirects to /login via window.location.href |
The 401 interceptor uses
window.location.href = '/login', a full page reload, not React Router navigation. This guarantees all in-memory state is dropped when a session expires.Token Key Is Brand-Scoped
ThelocalStorage key is TOKEN_KEY, exported from src/brand.ts:
VITE_TOKEN_KEY in its .env, so two brands served from the same browser origin do not collide on a shared session token.
AuthContext
src/auth/AuthContext.tsx defines AuthProvider and the useAuth() hook.
| Member | Type | Notes |
|---|---|---|
user | User | null | Loaded from GET /api/v1/auth/me |
token | string | null | Initialized from localStorage[TOKEN_KEY] |
login(email, password) | Promise<User> | POST /api/v1/auth/login, stores access_token, then fetches /auth/me |
logout() | void | Clears token and user |
refreshUser() | Promise<void> | Re-fetches /auth/me, swallows errors |
loading | boolean | True until the initial /auth/me resolves |
isAgent, isSupervisor, isAdmin, isSuperAdmin | boolean | Derived from user.role |
isAgentOrSupervisor | boolean | isAgent || isSupervisor |
fetchUser(). If /auth/me fails, it clears the token and user so a stale or revoked token cannot leave the app in a half-authenticated state.
ProtectedRoute
src/auth/ProtectedRoute.tsx wraps protected layout branches and takes an optional roles allow-list.
Decision order:
loading→ render a centered spinner.- No
token→<Navigate to="/login" replace />. rolesset butusernot yet loaded → rendernull(waiting for/auth/me).user.rolenot inroles→ redirect to the role’s home (/agent-consolefor agents/supervisors, else/dashboard).- Otherwise → render
children.
Environment Variables
| Var | Used by | Purpose |
|---|---|---|
VITE_API_URL | client.ts, IntegrationTab, snippets | CXB API base URL |
VITE_TOKEN_KEY | brand.ts → TOKEN_KEY | localStorage session key (brand-scoped) |
.env. See the white-label doc for how they are injected.
Related Docs
App Shell & Routing
Route tree and role-gated layouts.
White-Label Build
Where VITE_API_URL and VITE_TOKEN_KEY come from.