# [Frontend] Add Auth Guard on Page Reload ## Summary The frontend does not validate authentication state on page load/reload. When a user's session expires or authentication tokens are cleared, reloading the page should redirect to `/login`, but currently it does not. ## Failing Test - **File**: `tests/core/authentication.spec.ts` - **Test**: `should redirect to login when session expires` - **Line**: ~310 ## Steps to Reproduce 1. Log in to the application 2. Open browser dev tools 3. Clear localStorage and cookies 4. Reload the page 5. **Expected**: Redirect to `/login` 6. **Actual**: Page remains on current route (e.g., `/dashboard`) --- ## Research Findings ### Auth Architecture Overview | File | Purpose | |------|---------| | [context/AuthContext.tsx](../../frontend/src/context/AuthContext.tsx) | Main `AuthProvider` - manages user state, login/logout, token handling | | [context/AuthContextValue.ts](../../frontend/src/context/AuthContextValue.ts) | Type definitions: `User`, `AuthContextType` | | [hooks/useAuth.ts](../../frontend/src/hooks/useAuth.ts) | Custom hook to access auth context | | [components/RequireAuth.tsx](../../frontend/src/components/RequireAuth.tsx) | Route guard - redirects to `/login` if not authenticated | | [api/client.ts](../../frontend/src/api/client.ts) | Axios instance with auth token handling | | [App.tsx](../../frontend/src/App.tsx) | Router setup with `AuthProvider` and `RequireAuth` | ### Current Auth Flow ``` Page Load → AuthProvider.useEffect() → checkAuth() │ ┌──────────────┴──────────────┐ ▼ ▼ localStorage.get() GET /auth/me setAuthToken(stored) │ ┌───────────┴───────────┐ ▼ ▼ Success Error setUser(data) setUser(null) setAuthToken(null) │ │ ▼ ▼ isLoading=false isLoading=false isAuthenticated=true isAuthenticated=false ``` ### Current Implementation (AuthContext.tsx lines 9-25) ```typescript useEffect(() => { const checkAuth = async () => { try { const stored = localStorage.getItem('charon_auth_token'); if (stored) { setAuthToken(stored); } const response = await client.get('/auth/me'); setUser(response.data); } catch { setAuthToken(null); setUser(null); } finally { setIsLoading(false); } }; checkAuth(); }, []); ``` ### RequireAuth Component (RequireAuth.tsx) ```typescript const RequireAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { isAuthenticated, isLoading } = useAuth(); const location = useLocation(); if (isLoading) { return ; } if (!isAuthenticated) { return ; } return children; }; ``` ### API Client 401 Handler (client.ts lines 23-31) ```typescript client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { console.warn('Authentication failed:', error.config?.url); } return Promise.reject(error); } ); ``` --- ## Root Cause Analysis **The existing implementation already handles this correctly!** Looking at the code flow: 1. **AuthProvider** runs `checkAuth()` on mount (`useEffect` with `[]`) 2. It calls `GET /auth/me` to validate the session 3. On error (401), it sets `user = null` and `isAuthenticated = false` 4. **RequireAuth** reads `isAuthenticated` and redirects to `/login` if false **The issue is likely one of:** 1. **Race condition**: `RequireAuth` renders before `checkAuth()` completes 2. **Token without validation**: If token exists in localStorage but is invalid, the `GET /auth/me` fails, but something may not be updating properly 3. **Caching issue**: `isLoading` may not be set correctly on certain paths ### Verified Behavior - `isLoading` starts as `true` (line 8) - `RequireAuth` shows loading overlay while `isLoading` is true - `checkAuth()` sets `isLoading=false` in `finally` block - If `/auth/me` fails, `user=null` → `isAuthenticated=false` → redirect to `/login` **This should work!** Need to verify with E2E test what's actually happening. --- ## Potential Issues to Investigate ### 1. API Client Not Clearing Token on 401 The interceptor only logs, doesn't clear state: ```typescript if (error.response?.status === 401) { console.warn('Authentication failed:', error.config?.url); // Just logs! } ``` ### 2. No Global Auth State Reset When a 401 occurs on any API call (not just `/auth/me`), there's no mechanism to force logout. ### 3. localStorage Token Persists After Session Expiry Backend sessions expire, but frontend keeps the localStorage token. --- ## Recommended Solution ### Option A: Enhanced API Interceptor (Minimal Change) ✅ RECOMMENDED Modify [api/client.ts](../../frontend/src/api/client.ts) to clear auth state on 401: ```typescript // Add global auth reset callback let onAuthError: (() => void) | null = null; export const setOnAuthError = (callback: (() => void) | null) => { onAuthError = callback; }; client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { console.warn('Authentication failed:', error.config?.url); localStorage.removeItem('charon_auth_token'); setAuthToken(null); onAuthError?.(); // Trigger state reset } return Promise.reject(error); } ); ``` Then in **AuthContext.tsx**, register the callback: ```typescript useEffect(() => { setOnAuthError(() => { setUser(null); // Navigate will happen via RequireAuth }); return () => setOnAuthError(null); }, []); ``` ### Option B: Direct Window Navigation (Simpler) In the 401 interceptor, redirect immediately: ```typescript if (error.response?.status === 401 && !error.config?.url?.includes('/auth/me')) { localStorage.removeItem('charon_auth_token'); window.location.href = '/login'; } ``` **Note**: This causes a full page reload and loses SPA state. --- ## Files to Modify | File | Change | |------|--------| | `frontend/src/api/client.ts` | Add 401 handler with auth reset | | `frontend/src/context/AuthContext.tsx` | Register auth error callback | ## Implementation Checklist - [ ] Update `api/client.ts` with enhanced 401 interceptor - [ ] Update `AuthContext.tsx` to register the callback - [ ] Add unit tests for auth error handling - [ ] Verify E2E test `should redirect to login when session expires` passes --- ## Priority **Medium** - Security improvement but not critical since API calls still require valid auth. ## Labels - frontend - security - auth - enhancement ## Related - Fixes E2E test: `should redirect to login when session expires` - Part of Phase 1 E2E testing backlog