CXB Console talks to CXB API through a single Axios instance and stores its session as a JWT in 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.
ConcernBehavior
Base URLimport.meta.env.VITE_API_URL (set per brand)
Default headerContent-Type: application/json
Auth injectionRequest interceptor reads the token from localStorage[TOKEN_KEY] and sets Authorization: Bearer <token>
401 handlingResponse 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

The localStorage key is TOKEN_KEY, exported from src/brand.ts:
export const TOKEN_KEY = import.meta.env.VITE_TOKEN_KEY || 'cxb_token';
Each brand sets its own 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.
MemberTypeNotes
userUser | nullLoaded from GET /api/v1/auth/me
tokenstring | nullInitialized from localStorage[TOKEN_KEY]
login(email, password)Promise<User>POST /api/v1/auth/login, stores access_token, then fetches /auth/me
logout()voidClears token and user
refreshUser()Promise<void>Re-fetches /auth/me, swallows errors
loadingbooleanTrue until the initial /auth/me resolves
isAgent, isSupervisor, isAdmin, isSuperAdminbooleanDerived from user.role
isAgentOrSupervisorbooleanisAgent || isSupervisor
On mount the provider calls 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:
  1. loading → render a centered spinner.
  2. No token<Navigate to="/login" replace />.
  3. roles set but user not yet loaded → render null (waiting for /auth/me).
  4. user.role not in roles → redirect to the role’s home (/agent-console for agents/supervisors, else /dashboard).
  5. Otherwise → render children.
This means both the route guard and the layout nav use the same role booleans, so a user can never reach a route their role does not allow even by typing the URL.

Environment Variables

VarUsed byPurpose
VITE_API_URLclient.ts, IntegrationTab, snippetsCXB API base URL
VITE_TOKEN_KEYbrand.tsTOKEN_KEYlocalStorage session key (brand-scoped)
Both are build-time Vite env vars supplied by the brand .env. See the white-label doc for how they are injected.

App Shell & Routing

Route tree and role-gated layouts.

White-Label Build

Where VITE_API_URL and VITE_TOKEN_KEY come from.