React · Auth & Quality

Protected Routes

Some pages are only for signed-in users, and some only for admins. A small wrapper route checks the user and redirects everyone else — then sends them back after they log in.

Advanced 6 min Lesson 33 of 42Examples target React 19 with Vite.
0/42 in React
On this page · 6 min · Advanced

Learning objectives#

  • Write a RequireAuth wrapper route
  • Redirect to login and back again
  • Restrict routes by role
  • Handle the “still checking” state

Explanation#

A protected route is a component that renders <Outlet /> (or children) when the user is allowed, and <Navigate to="/login" replace /> otherwise.

Pass the current location in navigation state: <Navigate to="/login" state={{ from: location }} />. After login, navigate(from ?? "/") returns users to the page they wanted.

While the session is still being checked, render a loading indicator — not the redirect — or signed-in users get bounced to login on every refresh.

Role-based routes add a check such as user.roles.includes("Admin") and show a 403 page or redirect.

Route guards only improve the experience. The API must still reject unauthorised requests.

Syntax#

JSX Illustrative
function RequireAuth() {
  const { user, checking } = useAuth();
  const location = useLocation();
  if (checking) return <p>Loading…</p>;
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
  return <Outlet />;
}

Code example: Guarding account and admin pages#

JSXroutes.jsx Illustrative
import { createContext, useContext, useState } from 'react';
import {
  createBrowserRouter, RouterProvider, Outlet, Navigate, Link,
  useLocation, useNavigate,
} from 'react-router';


const AuthContext = createContext(null);
const useAuth = () => useContext(AuthContext);


function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const login = (name, roles) => setUser({ name, roles });
  const logout = () => setUser(null);
  return <AuthContext value={{ user, checking: false, login, logout }}>{children}</AuthContext>;
}


function RequireAuth({ role }) {
  const { user, checking } = useAuth();
  const location = useLocation();
  if (checking) return <p>Checking session…</p>;
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
  if (role && !user.roles.includes(role)) return <p role="alert">403 — you do not have access.</p>;
  return <Outlet />;
}


function LoginPage() {
  const { login } = useAuth();
  const navigate = useNavigate();
  const from = useLocation().state?.from?.pathname ?? '/';
  return (
    <>
      <button onClick={() => { login('Asha', ['Customer']); navigate(from, { replace: true }); }}>Log in as customer</button>
      <button onClick={() => { login('Ravi', ['Admin']); navigate(from, { replace: true }); }}>Log in as admin</button>
    </>
  );
}


function Layout() {
  const { user, logout } = useAuth();
  return (
    <>
      <nav>
        <Link to="/">Home</Link> | <Link to="/account">Account</Link> | <Link to="/admin">Admin</Link>
        {user && <> | {user.name} <button onClick={logout}>Log out</button></>}
      </nav>
      <Outlet />
    </>
  );
}


const router = createBrowserRouter([
  {
    element: <Layout />,
    children: [
      { path: '/', element: <p>Public home page</p> },
      { path: '/login', element: <LoginPage /> },
      { element: <RequireAuth />, children: [{ path: '/account', element: <p>Your orders</p> }] },
      { element: <RequireAuth role="Admin" />, children: [{ path: '/admin', element: <p>Admin dashboard</p> }] },
    ],
  },
]);


export default function App() {
  return <AuthProvider><RouterProvider router={router} /></AuthProvider>;
}
Expected output
Visiting /account while signed out redirects to /login. Clicking “Log in as customer” returns you to /account (“Your orders”). Opening /admin as the customer shows “403 — you do not have access.”; logging in as admin shows “Admin dashboard”.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. RequireAuth is a layout route: it either renders its children via <Outlet /> or redirects.
  2. The redirect stores the attempted location in navigation state.
  3. LoginPage reads state.from and navigates back after logging in; replace avoids a back-button loop.
  4. The admin group passes role="Admin", so signed-in customers get a 403 message instead.

Real-world use case#

An HR portal guards /payslips for any employee, /approvals for managers and /settings for HR admins. Deep links from email notifications land users on the right page after they sign in.

Common mistakes#

Redirecting while checking

If the session check is still running, user is null and the guard redirects signed-in users. Render a loading state first.

Losing the original destination

Always sending users to “/” after login is annoying. Pass and use state.from.

Guarding only the UI

Anyone can call the API directly. Enforce roles on the server.

Best practices#

  • Guard groups of routes with a layout route
  • Return users to where they were going
  • Handle checking, signed-out and forbidden separately
  • Mirror server-side authorization

Remember this#

Allowed → <Outlet />. Not signed in → <Navigate to="/login" />.

state={{ from: location }} → back after login.

Guards are UX; the API is the lock.

Practice exercise#

Your task: Write a GuestOnly route that redirects signed-in users away from /login and /register to /account.

Need a hint?
The opposite check: if (user) return <Navigate to="/account" replace />.
Show solution
JSX Illustrative
import { Navigate, Outlet } from 'react-router';


export function GuestOnly({ user }) {
  if (user) return <Navigate to="/account" replace />;
  return <Outlet />;
}
// Router: { element: <GuestOnly user={user} />, children: [
//   { path: '/login', element: <LoginPage /> },
//   { path: '/register', element: <RegisterPage /> },
// ] }

Mini quiz#

Protected Routes quizMultiple choice Question 1 of 3

Which component performs the redirect in a route guard?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.