In any modern web application, certain pages or features are meant to be accessible only to authenticated users. Whether it’s a user dashboard, settings page, or admin panel, you need a mechanism to protect those routes and redirect unauthorized users to a login or error page.
In React apps that use react-router-dom
, this is done through Protected Routes.
In this guide, you’ll learn exactly how to implement Protected Routes using react-router-dom
, including:
- Setting up a basic authentication context
- Creating a reusable ProtectedRoute component
- Redirecting unauthorized users
- Extending with roles or permissions
What Are Protected Routes?
A Protected Route is a route that requires a user to meet certain conditions before being granted access—usually being logged in. If those conditions are not met, the user is redirected (usually to a login page).
For example:
/dashboard
→ Only for authenticated users/admin
→ Only for admins/profile
→ Only for logged-in users
With react-router-dom
, you can use conditional rendering and the Navigate
component to implement this logic.
Install and Set Up react-router-dom
First, make sure you have react-router-dom
installed:
npm install react-router-dom
Then structure your app using <BrowserRouter>
, <Routes>
, and <Route>
.
Step 1: Create an Authentication Context
You need a global way to manage authentication state. The best practice is to use React Context to provide the current user status across the app.
AuthContext.js
import { createContext, useContext, useState } from "react"
const AuthContext = createContext()
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const login = (userData) => {
setUser(userData)
}
const logout = () => {
setUser(null)
}
const isAuthenticated = !!user
return (
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
return useContext(AuthContext)
}
Wrap your entire app in this provider in main.jsx
or App.jsx
:
<AuthProvider>
<App />
</AuthProvider>
Step 2: Create a ProtectedRoute Component
This component will wrap any route that needs protection and check if the user is authenticated.
ProtectedRoute.jsx
import { Navigate } from "react-router-dom"
import { useAuth } from "./AuthContext"
export default function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return children
}
If the user is not authenticated, they’ll be redirected to /login
. Otherwise, the protected content will render.
Step 3: Define Your Routes with Protection
Here’s how to use the ProtectedRoute
in your router setup.
App.jsx
import { BrowserRouter, Routes, Route } from "react-router-dom"
import Home from "./pages/Home"
import Login from "./pages/Login"
import Dashboard from "./pages/Dashboard"
import ProtectedRoute from "./ProtectedRoute"
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
)
}
Now, when an unauthenticated user tries to access /dashboard
, they’ll be sent to /login
.
Step 4: Build the Login Page
Your login page should authenticate users and call the login
method from context.
Login.jsx
import { useNavigate } from "react-router-dom"
import { useAuth } from "../AuthContext"
function Login() {
const navigate = useNavigate()
const { login } = useAuth()
const handleLogin = () => {
const fakeUser = { name: "John Doe", role: "user" }
login(fakeUser)
navigate("/dashboard")
}
return (
<div>
<h2>Login Page</h2>
<button onClick={handleLogin}>Log In</button>
</div>
)
}
export default Login
In a real app, you’d replace fakeUser
with actual data from an API.
Step 5: Add Logout Functionality
You can allow users to log out by calling the logout
function from context.
import { useAuth } from "../AuthContext"
import { useNavigate } from "react-router-dom"
function Navbar() {
const { logout } = useAuth()
const navigate = useNavigate()
const handleLogout = () => {
logout()
navigate("/")
}
return (
<button onClick={handleLogout}>Logout</button>
)
}
Bonus: Role-Based Access Control
Want to allow only admins to access certain routes?
Modify your ProtectedRoute
component like this:
export default function ProtectedRoute({ children, requiredRole }) {
const { user, isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
if (requiredRole && user.role !== requiredRole) {
return <Navigate to="/unauthorized" replace />
}
return children
}
Use it like this:
<Route
path="/admin"
element={
<ProtectedRoute requiredRole="admin">
<AdminPanel />
</ProtectedRoute>
}
/>
Now only users with role === "admin"
can access /admin
.
Final Thoughts
Protected Routes are essential for securing your React applications and ensuring users can only access content they’re authorized to see. With react-router-dom
and React Context, it’s simple and effective to set up:
- Use Context to manage auth state globally
- Use
Navigate
for redirection - Use a reusable
ProtectedRoute
wrapper - Extend for role-based access if needed
With this pattern in place, you can confidently build user-facing features with proper access control, whether you’re creating a basic dashboard or a full-featured SaaS application.