# Route Protection Documentation ## Function 8: Authorization & Route Protection The system uses 2 components to protect routes: - **ProtectedRoute**: Requires user to be logged in - **AdminRoute**: Requires user to be Admin --- ## 1. ProtectedRoute ### Purpose Protects routes requiring authentication (login). ### How It Works ```tsx // File: client/src/components/auth/ProtectedRoute.tsx const ProtectedRoute: React.FC = ({ children }) => { const location = useLocation(); const { isAuthenticated, isLoading } = useAuthStore(); // 1. If loading → display spinner if (isLoading) { return ; } // 2. If not logged in → redirect /login if (!isAuthenticated) { return ( ); } // 3. Logged in → allow access return <>{children}; }; ``` ### Usage in App.tsx ```tsx import { ProtectedRoute } from './components/auth'; // Route requiring login } /> } /> } /> ``` ### Flow 1. User not logged in accesses `/dashboard` 2. ProtectedRoute checks `isAuthenticated === false` 3. Redirect to `/login` and save `state={{ from: '/dashboard' }}` 4. After successful login, redirect to `/dashboard` --- ## 2. AdminRoute ### Purpose Protects routes for Admin only (role-based access). ### How It Works ```tsx // File: client/src/components/auth/AdminRoute.tsx const AdminRoute: React.FC = ({ children }) => { const location = useLocation(); const { isAuthenticated, userInfo, isLoading } = useAuthStore(); // 1. If loading → display spinner if (isLoading) { return ; } // 2. If not logged in → redirect /login if (!isAuthenticated) { return ( ); } // 3. If not admin → redirect / const isAdmin = userInfo?.role === 'admin'; if (!isAdmin) { return ; } // 4. Is admin → allow access return <>{children}; }; ``` ### Usage in App.tsx ```tsx import { AdminRoute } from './components/auth'; // Route for Admin only } > } /> } /> } /> } /> } /> ``` ### Flow #### Case 1: User not logged in 1. Access `/admin` 2. AdminRoute checks `isAuthenticated === false` 3. Redirect to `/login` with `state={{ from: '/admin' }}` 4. After successful login → return to `/admin` 5. AdminRoute checks role again #### Case 2: User logged in but not Admin 1. Customer (role='customer') accesses `/admin` 2. AdminRoute checks `isAuthenticated === true` 3. AdminRoute checks `userInfo.role === 'customer'` (not 'admin') 4. Redirect to `/` (homepage) #### Case 3: User is Admin 1. Admin (role='admin') accesses `/admin` 2. AdminRoute checks `isAuthenticated === true` 3. AdminRoute checks `userInfo.role === 'admin'` ✅ 4. Allow access --- ## 3. Route Structure in App.tsx ```tsx function App() { return ( {/* Public Routes - No login required */} }> } /> } /> } /> {/* Auth Routes - No layout */} } /> } /> } /> } /> {/* Protected Routes - Login required */} }> } /> } /> } /> {/* Admin Routes - Admin only */} } > } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* 404 Route */} } /> ); } ``` --- ## 4. Integration with Zustand Store ### useAuthStore State ```tsx // File: client/src/store/useAuthStore.ts const useAuthStore = create((set) => ({ // State token: localStorage.getItem('token') || null, refreshToken: localStorage.getItem('refreshToken') || null, userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null'), isAuthenticated: !!localStorage.getItem('token'), isLoading: false, error: null, // Actions login: async (credentials) => { ... }, logout: () => { localStorage.removeItem('token'); localStorage.removeItem('refreshToken'); localStorage.removeItem('userInfo'); set({ token: null, refreshToken: null, userInfo: null, isAuthenticated: false, error: null }); }, // ... other actions })); ``` ### User Roles - **admin**: Administrator (full access) - **staff**: Staff (limited access) - **customer**: Customer (customer features only) --- ## 5. Loading State Both components handle loading state to avoid: - Flash of redirect (flickering when changing pages) - Race condition (auth state not loaded yet) ```tsx if (isLoading) { return (

Authenticating...

); } ``` --- ## 6. Redirect After Login ### LoginPage implementation ```tsx const LoginPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const { login, isLoading } = useAuthStore(); const from = location.state?.from?.pathname || '/dashboard'; const onSubmit = async (data: LoginFormData) => { try { await login(data); // Redirect to original page or /dashboard navigate(from, { replace: true }); toast.success('Login successful!'); } catch (error) { toast.error('Login failed!'); } }; return (
{/* ... form fields */}
); }; ``` ### Flow 1. User accesses `/bookings` (protected) 2. Redirect `/login?from=/bookings` 3. Login successful 4. Redirect to `/bookings` (original page) --- ## 7. Testing Route Protection ### Test Case 1: ProtectedRoute - Unauthenticated **Given**: User not logged in **When**: Access `/dashboard` **Then**: Redirect to `/login` **And**: Save `from=/dashboard` in location state ### Test Case 2: ProtectedRoute - Authenticated **Given**: User logged in **When**: Access `/dashboard` **Then**: Display DashboardPage successfully ### Test Case 3: AdminRoute - Not Admin **Given**: User has role='customer' **When**: Access `/admin` **Then**: Redirect to `/` (homepage) ### Test Case 4: AdminRoute - Is Admin **Given**: User has role='admin' **When**: Access `/admin` **Then**: Display AdminLayout successfully ### Test Case 5: Loading State **Given**: Auth is initializing **When**: isLoading === true **Then**: Display loading spinner **And**: No redirect --- ## 8. Security Best Practices ### ✅ Implemented 1. **Client-side protection**: ProtectedRoute & AdminRoute 2. **Token persistence**: localStorage 3. **Role-based access**: Check userInfo.role 4. **Location state**: Save "from" to redirect to correct page 5. **Loading state**: Avoid flash of redirect 6. **Replace navigation**: Don't save redirect history ### ⚠️ Note - Client-side protection **is not enough** → Must have backend validation - API endpoints must check JWT + role - Backend middleware: `auth`, `adminOnly` - Never trust client-side role → Always verify on server ### Backend Middleware Example ```javascript // server/src/middlewares/auth.js const auth = async (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ message: 'Unauthorized' }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = await User.findByPk(decoded.userId); next(); } catch (error) { res.status(401).json({ message: 'Invalid token' }); } }; const adminOnly = (req, res, next) => { if (req.user.role !== 'admin') { return res.status(403).json({ message: 'Forbidden: Admin only' }); } next(); }; // Usage router.get('/admin/users', auth, adminOnly, getUsers); ``` --- ## 9. Troubleshooting ### Issue 1: Infinite redirect loop **Cause**: ProtectedRoute check logic error **Solution**: Ensure `replace={true}` in Navigate ### Issue 2: Flash of redirect **Cause**: Not handling loading state **Solution**: Add check `if (isLoading)` before auth check ### Issue 3: Lost location state **Cause**: Not passing `state={{ from: location }}` **Solution**: Always save location when redirecting ### Issue 4: Admin can access but API fails **Cause**: Backend doesn't verify role **Solution**: Add `adminOnly` middleware on API routes --- ## 10. Summary ### ProtectedRoute - ✅ Check `isAuthenticated` - ✅ Redirect `/login` if not logged in - ✅ Save location state to return - ✅ Handle loading state ### AdminRoute - ✅ Check `isAuthenticated` first - ✅ Check `userInfo.role === 'admin'` - ✅ Redirect `/login` if not logged in - ✅ Redirect `/` if not admin - ✅ Handle loading state ### Results - Protect all protected routes - Smooth UX, no flash - Role-based access works correctly - Good security (combined with backend validation) --- **Function 8 completed! ✅**