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/jsonunless the body isFormData. - 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:
- User visits a protected page (e.g.,
/compliance). - Middleware checks for
aegis_tokencookie. If missing, redirects to/login?redirect=/compliance. - Login page renders a form with email and password inputs.
- User submits credentials (
admin@aegis.local/aegis-dev-adminfor local dev). login()function sendsPOST /auth/tokento the auth service on port 8009.- Auth service verifies the password (bcrypt), returns
{ access_token, user_id, roles, email, display_name }. setToken()stores the JWT in anaegis_tokencookie (24h expiry).setUser()stores user data inAuthContextandlocalStorage.- Redirect to the original page or
/conversations. - Subsequent API calls via
api.get()/api.post()automatically attachAuthorization: Bearer {token}. - API Gateway validates the token on each request via the auth service’s
/auth/validateendpoint. - 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.