| name | dashboard-api-authentication |
| description | Proper authentication patterns for dashboard frontend API calls. Use when adding new API endpoints, creating components that fetch data, or debugging 401 Unauthorized errors. Covers JWT token handling, the apiRequest helper, and common pitfalls. |
Dashboard API Authentication
Quick Reference
import { getFeatureFlags, setFeatureFlagOverride } from '../api';
The Golden Rule
All authenticated API calls MUST use the apiRequest helper from api.ts or one of the exported API functions that use it internally.
The dashboard uses JWT-based authentication. The token is stored in localStorage and sent via the Authorization: Bearer <token> header. Raw fetch() calls with credentials: 'include' will NOT include this header.
Authentication Architecture
Token Storage
localStorage.setItem('dashboard_token', token);
setCookie('auth_token', token, 1);
The apiRequest Helper
Located in packages/dashboard-frontend/src/api.ts:
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const token = getAuthToken();
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
(headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers,
});
if (response.status === 401) {
clearAuthToken();
window.location.reload();
throw new Error('Authentication expired');
}
}
Common Patterns
Adding a New API Function
export async function getMyData(): Promise<MyDataResponse> {
return apiRequest('/my-endpoint');
}
export async function updateMyData(id: string, data: UpdateInput): Promise<MyDataResponse> {
return apiRequest(`/my-endpoint/${encodeURIComponent(id)}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
Using API Functions in Components
import { getMyData, updateMyData } from '../api';
const loadData = async () => {
try {
const data = await getMyData();
setData(data);
} catch (error) {
}
};
Using API Functions in Custom Hooks
import { useCallback, useEffect, useState } from 'react';
import { getMyData, type MyDataResponse } from '../api';
export function useMyFeature() {
const [data, setData] = useState<MyDataResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadData = useCallback(async () => {
try {
setLoading(true);
const result = await getMyData();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadData();
}, [loadData]);
return { data, loading, error, : loadData };
}
Common Mistakes
Mistake 1: Using Raw Fetch
const response = await fetch('/api/feature-flags', {
credentials: 'include',
});
import { getFeatureFlags } from '../api';
const data = await getFeatureFlags();
Mistake 2: Forgetting to Export from api.ts
If you add a new endpoint, you must:
- Add the function to
api.ts
- Export it
- Import it where needed
export async function getNewEndpoint(): Promise<Response> {
return apiRequest('/new-endpoint');
}
import { getNewEndpoint } from '../api';
Mistake 3: Not Handling 401 in Custom Fetch
If you absolutely must use raw fetch (rare), handle 401:
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${getAuthToken()}`,
},
});
if (response.status === 401) {
clearAuthToken();
window.location.reload();
throw new Error('Authentication expired');
}
Debugging 401 Errors
Symptoms
- API calls return 401 Unauthorized
- Server logs show "No authorization header"
- Feature works initially but fails after page components load
Diagnostic Steps
-
Check if using apiRequest:
grep -r "fetch('/api" packages/dashboard-frontend/src/
Any raw fetch to /api/* is suspicious.
-
Check Network tab:
- Look for
Authorization header in request
- If missing, the call isn't using apiRequest
-
Verify token exists:
localStorage.getItem('dashboard_token');
Fix Pattern
Replace raw fetch with api.ts function:
const response = await fetch(`/api/feature-flags/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ enabled }),
});
import { setFeatureFlagOverride } from '../api';
await setFeatureFlagOverride(id, enabled);
Server-Side Authentication
The server expects JWT in the Authorization header:
authMiddleware = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.status(401).json({ error: 'No authorization header' });
return;
}
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
const payload = this.verifyToken(token);
};
Checklist for New API Endpoints
When adding a new authenticated endpoint:
Testing Authentication
To verify authentication is working:
import { getAuthToken } from '../api';
const token = getAuthToken();
console.log('Token present:', !!token);
try {
const data = await getFeatureFlags();
console.log('Auth working:', data);
} catch (e) {
console.error('Auth failed:', e);
}