A protected route is a route that should only be accessible when a user satisfies a required condition, such as being authenticated. In React, protected routes are commonly implemented together with authentication state and React Router.
A protected route is a route where access depends on a condition.
For example, a dashboard may only be shown when a user is authenticated.
User
↓
Is Authenticated?
↙ ↘
Yes No
↓ ↓
Dashboard Login
Protected routes help control which pages users can visit in the frontend application.
Common examples include:
| Public Route | Protected Route |
|---|---|
| Can normally be accessed without login | Requires an authentication condition |
| Example: Home | Example: Dashboard |
| Example: Login | Example: Profile |
Protected route logic can be implemented around React Router routes.
<Routes>
<Route
path="/login"
element={<Login />}
/>
<Route
path="/dashboard"
element={<ProtectedRoute>
<Dashboard />
</ProtectedRoute>}
/>
</Routes>
The application needs some authentication state to determine whether the user is currently authenticated.
const [user, setUser] =
useState(null);
A non-null user can represent an authenticated application state, depending on how the application is designed.
A simple authentication check can test whether a user exists.
if (user) {
// User is authenticated
} else {
// User is not authenticated
}
React Router's Navigate component can redirect an unauthenticated
user to the login page.
import {
Navigate
} from "react-router-dom";
function ProtectedRoute({
user,
children
}) {
if (!user) {
return (
<Navigate
to="/login"
/>
);
}
return children;
}
A reusable component can contain the protected route logic.
function ProtectedRoute({
user,
children
}) {
return user
? children
: <Navigate to="/login" />;
}
<Route
path="/dashboard"
element={
<ProtectedRoute
user={user}
>
<Dashboard />
</ProtectedRoute>
}
/>
The Dashboard component is rendered only when the authentication condition allows it.
If the user is not authenticated, the protected route can redirect to
/login.
return (
<Navigate
to="/login"
replace
/>
);
The replace option can be used when the login redirect should
replace the current history entry.
If authentication is stored in Context, a protected route can read the
authentication state with useContext().
const {
user
} = useContext(
AuthContext
);
function ProtectedRoute({
children
}) {
const {
user
} = useContext(
AuthContext
);
if (!user) {
return (
<Navigate
to="/login"
replace
/>
);
}
return children;
}
When the application checks an existing authentication session, the result may not be available immediately.
const {
user,
loading
} = useContext(
AuthContext
);
The route should avoid redirecting before the authentication check has finished.
if (loading) {
return (
<p>
Checking authentication...
</p>
);
}
if (!user) {
return (
<Navigate
to="/login"
replace
/>
);
}
return children;
A dashboard can be placed behind a protected route.
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
The same protected route component can protect multiple pages.
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
A protected layout can also contain multiple child routes.
<Route
element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}
>
<Route
path="/dashboard"
element={<Dashboard />}
/>
<Route
path="/profile"
element={<Profile />}
/>
</Route>
A protected layout can contain common navigation and an
Outlet for child pages.
function ProtectedLayout() {
return (
<div>
<Navbar />
<Outlet />
</div>
);
}
Authentication and authorization are different. An admin page may require both an authenticated user and an appropriate role.
if (!user) {
return (
<Navigate
to="/login"
/>
);
}
if (user.role !== "admin") {
return (
<Navigate
to="/dashboard"
/>
);
}
function AdminRoute({
user,
children
}) {
if (!user) {
return (
<Navigate
to="/login"
/>
);
}
if (user.role !== "admin") {
return (
<Navigate
to="/dashboard"
/>
);
}
return children;
}
Sometimes a user visits a protected page before logging in. The application can remember the requested location and return the user after successful authentication.
const location =
useLocation();
return (
<Navigate
to="/login"
state={{
from: location
}}
replace
/>
);
The login page can read the previous location and navigate there after successful authentication.
const location =
useLocation();
const from =
location.state?.from?.pathname
|| "/dashboard";
navigate(from, {
replace: true
});
User Visits Protected Page
↓
Check Authentication
↓
┌─────┴─────┐
↓ ↓
Logged Not Logged
↓ ↓
Allow Page Login
↓ ↓
Dashboard Login Success
↓
Protected Page
function ProtectedRoute({
children
}) {
const {
user,
loading
} = useContext(
AuthContext
);
if (loading) {
return (
<p>
Checking authentication...
</p>
);
}
if (!user) {
return (
<Navigate
to="/login"
replace
/>
);
}
return children;
}
Authentication establishes who the user is. Route protection controls the frontend navigation based on authentication state.
Authentication
↓
Current User
↓
Protected Route
↓
Allowed UI
The backend must still verify authentication and authorization for protected API operations.
replace option when appropriate.AuthProvider
↓
Authentication State
↓
ProtectedRoute
↓
React Router
↓
Protected Page
↓
Protected API
↓
Backend Authorization
import {
Navigate,
useLocation
} from "react-router-dom";
import {
useContext
} from "react";
function ProtectedRoute({
children
}) {
const {
user,
loading
} = useContext(
AuthContext
);
const location =
useLocation();
if (loading) {
return (
<div>
Checking authentication...
</div>
);
}
if (!user) {
return (
<Navigate
to="/login"
state={{
from: location
}}
replace
/>
);
}
return children;
}
export default ProtectedRoute;
import {
BrowserRouter,
Routes,
Route,
Navigate
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route
path="/login"
element={
<Login />
}
/>
<Route
path="/"
element={
<Home />
}
/>
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
<Route
path="*"
element={
<Navigate
to="/"
/>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Navigate for frontend redirects.useLocation when the original destination needs to be remembered.Authentication
↓
ProtectedRoute
↓
React Router
↓
Protected UI
↓
Backend Authorization
Navigate can redirect unauthenticated users.ProtectedRoute can be created as a reusable component.Question: Which React Router component is commonly used to redirect an unauthenticated user to the login page?