Skip to Content

API Client

The AEGIS frontend uses a centralized API client for all backend communication. This page documents the API client, JWT token management, authentication context provider, and how auth headers are attached to requests.

Centralized API Client

The API client is defined in src/lib/api.ts and provides a thin wrapper around fetch that handles authentication automatically.

Token Management

Tokens are stored in browser cookies (not localStorage) for SSR compatibility and middleware access:

// Read the JWT token from cookies function getToken(): string | null { if (typeof document === "undefined") return null; const match = document.cookie.match(/(?:^|; )aegis_token=([^;]*)/); return match ? decodeURIComponent(match[1]) : null; } // Store a JWT token in cookies (24-hour expiry) export function setToken(token: string) { document.cookie = `aegis_token=${encodeURIComponent(token)}; path=/; max-age=${24 * 3600}; SameSite=Lax`; } // Remove the JWT token cookie export function clearToken() { document.cookie = "aegis_token=; path=/; max-age=0"; } // Check if a token exists export function isAuthenticated(): boolean { return !!getToken(); }

The cookie is named aegis_token with a 24-hour expiration and SameSite=Lax policy.

Request Function

All API calls go through the internal request<T> function:

async function request<T>(path: string, options: RequestInit = {}): Promise<T> { const token = getToken(); const headers: Record<string, string> = { ...(options.headers as Record<string, string>), }; if (token) headers["Authorization"] = `Bearer ${token}`; if (options.body && !(options.body instanceof FormData)) headers["Content-Type"] = "application/json"; const res = await fetch(`${API_BASE}${path}`, { ...options, headers }); if (res.status === 401) { clearToken(); if (typeof window !== "undefined") window.location.href = "/login"; throw new Error("Unauthorized"); } if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`); return res.json(); }

Key behaviors:

  • Authorization header: If a JWT token exists in cookies, it is attached as Bearer {token}.
  • Content-Type: Automatically set to application/json unless the body is FormData.
  • 401 handling: On any 401 response, the token is cleared and the user is redirected to /login.
  • Error propagation: Non-OK responses throw an error with the status code and response body.

Public API

The client exports a simple api object with get and post methods:

export const api = { get: <T>(path: string) => request<T>(path), post: <T>(path: string, body?: unknown) => request<T>(path, { method: "POST", body: body ? JSON.stringify(body) : undefined, }), };

Usage example:

import { api } from "@/lib/api"; // GET request const data = await api.get<ComplianceSummary>("/compliance/summary"); // POST request const result = await api.post<Conversation>("/api/v1/conversations", { agent_id: "rule37-agent", conversation_type: "filing_prep", });

Base URL Configuration

const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";

In local development, this defaults to the API Gateway at localhost:8000. Override with the NEXT_PUBLIC_API_URL environment variable for deployed environments.

Service URL Configuration

For pages that connect directly to specific backend services (bypassing the gateway), src/lib/api-urls.ts provides individual service URLs:

export const API_URLS = { gateway: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000", orchestration: process.env.NEXT_PUBLIC_ORCH_URL || "http://localhost:8001", memory: process.env.NEXT_PUBLIC_MEMORY_URL || "http://localhost:8002", knowledgeGraph: process.env.NEXT_PUBLIC_KG_URL || "http://localhost:8003", approval: process.env.NEXT_PUBLIC_APPROVAL_URL || "http://localhost:8004", compliance: process.env.NEXT_PUBLIC_COMPLIANCE_URL || "http://localhost:8006", flaring: process.env.NEXT_PUBLIC_FLARING_URL || "http://localhost:8007", } as const;

The api.ts client uses the gateway URL for its requests. Pages that use direct service URLs (like the conversations page connecting to the orchestration engine for SSE streaming) construct their own fetch calls using API_URLS.

Login Function

The login function authenticates directly against the auth service (not through the gateway):

export async function login(email: string, password: string) { const AUTH_URL = process.env.NEXT_PUBLIC_AUTH_URL || "http://localhost:8009"; const res = await fetch(`${AUTH_URL}/auth/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!res.ok) throw new Error("Invalid credentials"); return res.json() as Promise<{ access_token: string; user_id: string; roles: string[]; email: string; display_name: string | null; }>; }

The auth service returns an access_token (JWT), user_id, roles array, plus email and display_name for the UI header. The login page stores the token via setToken() and the user data via AuthContext.setUser().

Auth Context Provider

The AuthProvider in src/lib/auth-context.tsx provides user state to the entire application:

interface User { user_id: string; roles: string[]; email?: string; display_name?: string | null; } interface AuthCtx { user: User | null; isLoggedIn: boolean; logout: () => void; setUser: (u: User) => void; }

State Persistence

User data is persisted to localStorage under the key aegis_user. On mount, the provider checks both localStorage and the cookie to restore state:

useEffect(() => { const s = localStorage.getItem("aegis_user"); if (s && isAuthenticated()) setUserState(JSON.parse(s)); }, []);

If the cookie has expired but localStorage still has user data, the user will not be restored (the isAuthenticated() check prevents this).

Logout

The logout function clears the token cookie, removes localStorage data, and redirects to the login page:

const logout = () => { clearToken(); setUserState(null); localStorage.removeItem("aegis_user"); window.location.href = "/login"; };

Usage

import { useAuth } from "@/lib/auth-context"; function MyComponent() { const { user, isLoggedIn, logout } = useAuth(); if (!isLoggedIn) return <p>Not logged in</p>; return ( <div> <p>User: {user.user_id}</p> <p>Roles: {user.roles.join(", ")}</p> <button onClick={logout}>Sign out</button> </div> ); }

Authentication Flow

The complete authentication flow:

  1. User visits a protected page (e.g., /compliance).
  2. Middleware checks for aegis_token cookie. If missing, redirects to /login?redirect=/compliance.
  3. Login page renders a form with email and password inputs.
  4. User submits credentials (admin@aegis.local / aegis-dev-admin for local dev).
  5. login() function sends POST /auth/token to the auth service on port 8009.
  6. Auth service verifies the password (bcrypt), returns { access_token, user_id, roles, email, display_name }.
  7. setToken() stores the JWT in an aegis_token cookie (24h expiry).
  8. setUser() stores user data in AuthContext and localStorage.
  9. Redirect to the original page or /conversations.
  10. Subsequent API calls via api.get()/api.post() automatically attach Authorization: Bearer {token}.
  11. API Gateway validates the token on each request via the auth service’s /auth/validate endpoint.
  12. On 401 response from any API call, the client clears the token and redirects to /login.

The middleware only checks for cookie presence — it does not validate the JWT. A user with an expired or invalid token will pass the middleware but receive 401 errors from the API Gateway, which triggers the automatic redirect to /login.

Last updated on