Skip to content

Protected Routes

Protected routes restrict access to authenticated or authorized users. Unauthenticated visitors redirect to login; unauthorized roles see forbidden UI. This lesson implements auth guards with React Router v6.

Route Guard Pattern

Wrap protected elements in a component that checks auth state. If valid, render <Outlet /> or children; otherwise <Navigate to="/login" replace />.

Store intended destination in location state so login can redirect back after success.

function ProtectedRoute() {
  const { user, loading } = useAuth();
  const location = useLocation();

  if (loading) return <Spinner />;
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;

  return <Outlet />;
}

<Route element={<ProtectedRoute />}>
  <Route path="/dashboard" element={<Dashboard />} />
</Route>

state={{ from: location }} enables post-login redirect to originally requested page.

Role-Based Guards

function AdminRoute() {
  const { user } = useAuth();
  if (user?.role !== 'admin') return <Navigate to="/403" />;
  return <Outlet />;
}
  • Show loading UI while auth state resolves — avoid flash redirect.
  • Use layout route guards with Outlet for route groups.
  • Handle token expiry with global fetch interceptor + logout.
  • Never rely on client guards alone — secure APIs server-side.

Protected Route Patterns

Common auth routing approaches.

Pattern Implementation
Auth wrapper <ProtectedRoute /> + Outlet
Redirect login <Navigate to="/login" />
Return URL location.state.from
Role check Compare user.role before render
Lazy auth load Spinner until auth resolves
Server truth API 401 → logout handler

Post-Login Redirect

After successful login, navigate to location.state?.from?.pathname ?? '/dashboard'.

const location = useLocation();
const from = location.state?.from?.pathname || '/';
navigate(from, { replace: true });

Client Guards vs Server Security

Client-side route guards improve UX only. Always validate sessions and permissions on the server for every API request.

Common Mistakes

  • Redirect flash before auth loading completes.
  • Trusting client-side role checks for security.
  • Losing return URL after login redirect chain.
  • Not handling expired tokens globally.

Key Takeaways

  • Wrap routes with auth check components using Outlet.
  • Redirect unauthenticated users with Navigate + state.
  • Show loading while auth initializes.
  • Server must enforce authorization — client guards are UX only.

Pro Tip

Centralize auth in one AuthProvider + ProtectedRoute — avoid sprinkling checks in every page component.