How to Secure Outlet Component in React Router
Securing the Outlet component in React Router is a
fundamental pattern for protecting private routes from unauthorized
access. This article provides a straightforward guide on how to create a
reusable ProtectedRoute component that conditionalizes the
rendering of the <Outlet /> based on user
authentication, ensuring that restricted layout views are only
accessible to logged-in users.
Understanding the Secure Outlet Pattern
In React Router (v6+), the <Outlet /> component is
used within a parent route element to render its child route elements.
To secure these child routes, you wrap them in a protective parent
route.
Instead of checking authentication on every individual page, you
check it once in a custom layout component. If the user is
authenticated, the component renders the <Outlet />
(allowing child components to load). If the user is not authenticated,
they are redirected to a public page, such as a login screen.
Step-by-Step Implementation
1. Create the Protected Route Component
First, create a component called ProtectedRoute. This
component will check your application’s authentication state (e.g., from
a context, Redux, or local storage).
import { Navigate, Outlet } from 'react-router-dom';
const ProtectedRoute = ({ isAuthenticated }) => {
// If the user is not authenticated, redirect to the login page
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
// If authenticated, render the child routes via <Outlet />
return <Outlet />;
};
export default ProtectedRoute;2. Configure Your Routes
Next, integrate the ProtectedRoute into your router
configuration. Wrap the routes that require authentication inside the
ProtectedRoute route.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
import Dashboard from './pages/Dashboard';
import Profile from './pages/Profile';
import Login from './pages/Login';
import Home from './pages/Home';
function App() {
// Replace this with your actual authentication state logic
const { isAuthenticated } = useAuth();
return (
<BrowserRouter>
<Routes>
{/* Public Routes */}
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
{/* Protected Routes */}
<Route element={<ProtectedRoute isAuthenticated={isAuthenticated} />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
</BrowserRouter>
);
}How It Works
- The Wrapper: The
<Route element={<ProtectedRoute />}>acts as a guard. It does not have its own path, meaning it matches any path of its children. - The Redirect: If an unauthenticated user attempts
to access
/dashboard, theProtectedRouteintercepts the request and returns<Navigate to="/login" replace />, preventing/dashboardfrom mounting. - The Outlet: If the user is logged in,
<Outlet />is rendered, which acts as a placeholder that resolves to the<Dashboard />component.
Adding Role-Based Authorization
You can extend this pattern to support role-based access control (RBAC) by passing allowed roles to your protected route wrapper.
const ProtectedRoute = ({ isAuthenticated, userRole, allowedRoles }) => {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
if (allowedRoles && !allowedRoles.includes(userRole)) {
return <Navigate to="/unauthorized" replace />;
}
return <Outlet />;
};You can then declare specific roles for your nested routes:
<Route
element={
<ProtectedRoute
isAuthenticated={isAuthenticated}
userRole={user.role}
allowedRoles={['admin']}
/>
}
>
<Route path="/admin-settings" element={<AdminSettings />} />
</Route>