Home/Learn/React/Protected Routes and Authorisation

Protected Routes and Authorisation

Intermediate
Routing

A route guard is a UX affordance, not a security boundary. It hides what the user cannot use and sends them somewhere sensible — the server still decides what they may actually do.

Overview

Every application eventually needs pages only some people can see, and the React part is a wrapper route that checks the session and redirects. The important framing is that this is not security: the bundle ships to everyone, any user can call your API directly, and a route guard is trivially bypassed. It exists so authorised users are not shown doors they cannot open and unauthorised users get a sensible redirect. Every protected route must correspond to an endpoint that enforces the same rule server-side.

The Guard

A wrapper route, using the three-state auth from the Data category.

Redirect with the intended destination
function RequireAuth() {
  const { user, isChecking } = useAuth()
  const location = useLocation()

  if (isChecking) return <FullPageSpinner />       // not signed out — unknown

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

// Wrap the protected subtree
{
  element: <RequireAuth />,
  children: [
    { path: 'dashboard', element: <Dashboard /> },
    { path: 'account', element: <Account /> },
  ],
}

// Return the user where they were going after sign-in
const from = location.state?.from?.pathname ?? '/dashboard'
navigate(from, { replace: true })

// replace on the redirect, or back returns them to the guard,
// which redirects again — a loop the user cannot escape.

Roles and Subscription Tiers

The same shape, with a capability check instead of a presence check.

Redirect when signed out, explain when forbidden
function RequireRole({ roles }) {
  const { user, isChecking } = useAuth()
  if (isChecking) return <FullPageSpinner />
  if (!user) return <Navigate to="/login" replace />
  if (!roles.includes(user.role)) return <Forbidden />    // 403, not a redirect
  return <Outlet />
}
<Route element={<RequireRole roles={['admin']} />}>

// Signed out -> send them to sign in.
// Signed in but not permitted -> tell them, do not bounce them.
// A silent redirect looks like a broken link.

// For a paid tier, an upgrade prompt beats a wall
if (!isPro) return <UpgradePrompt feature="Solutions" />

// Hide the navigation too, or users click into a 403 repeatedly
{user?.role === 'admin' && <NavLink to="/admin">Admin</NavLink>}

// And the point that must be stated in an interview: the API
// enforces the same rule. Hiding the link does not protect the data —
// the bundle is public and the endpoint is reachable with curl.

Not Losing the User's Work

Guards that fire mid-session, and blocking navigation away from unsaved changes.

Reauthenticate in place; block on unsaved changes
// A session can expire while the user is typing. Do not redirect
// them off a form with unsaved input — prompt instead.
{sessionExpired && <ReauthDialog onSuccess={retry} />}

// Blocking navigation away from a dirty form
const blocker = useBlocker(
  ({ currentLocation, nextLocation }) =>
    isDirty && currentLocation.pathname !== nextLocation.pathname,
)
{blocker.state === 'blocked' && (
  <ConfirmDialog
    message="You have unsaved changes."
    onConfirm={blocker.proceed}
    onCancel={blocker.reset}
  />
)}

// beforeunload covers closing the tab; useBlocker covers in-app
// navigation. You need both for full coverage.

Key Points to Remember

  • 1A route guard is a UX affordance — the API must enforce the same rule, because the bundle is public
  • 2Check the auth "checking" state first, or the guard redirects signed-in users on every reload
  • 3Redirect with replace and carry the intended destination so sign-in returns the user where they were going
  • 4Signed out means redirect to sign in; signed in but not permitted means show a forbidden or upgrade screen
  • 5Prompt to reauthenticate in place rather than navigating away from a form with unsaved input

Interview Questions

Sign in to ask Aria
1

Is a protected route in React a security measure? Explain.

Medium
2

Why should a redirect after sign-in use replace?

Medium
3

How do you handle a session expiring while the user is filling in a long form?

Hard

Ask Aria about Protected Routes and Authorisation

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…