Async & Promisesmedium
Implement auth guards and API interceptors in a React app
Design frontend auth guards and API request/response interceptors for a SaaS application. Cover session loading, token refresh, 401 vs 403 handling, route protection, capabilities, and security limits.
Asked at PlainID
Answer
Interview framing: Auth guards and interceptors improve UX and consistency, but they are not the final security boundary. Backend enforcement is mandatory.
Auth guard responsibilities:
- Wait for initial session loading before redirecting.
- Redirect unauthenticated users to login and preserve the intended destination.
- Show unauthorized or request-access states for authenticated users without required capability.
- Avoid flicker by modeling loading, authenticated, unauthenticated, and forbidden states explicitly.
API client responsibilities:
- Attach auth/session headers consistently.
- Add request IDs or correlation IDs when useful.
- Normalize errors into a predictable shape.
- Handle 401 by refreshing session or redirecting to login.
- Handle 403 as authenticated but not allowed.
- Cancel stale requests where interactions change quickly.
Example shape:
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(path, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${getAccessToken()}`,
},
})
if (response.status === 401) throw new AuthError('Session expired')
if (response.status === 403) throw new ForbiddenError('Not allowed')
if (!response.ok) throw await toApiError(response)
return response.json() as Promise<T>
}
Security limits:
- Do not trust localStorage role flags for enforcement.
- Do not expose sensitive data and then hide it in the UI.
- Every sensitive API must validate permissions server-side.
Good closing: "The frontend should make auth state understandable and consistent, while the backend remains responsible for the actual access decision."
Source: Senior frontend interview research