How to Create Protected Routes in React Router DOM

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.

Navbar.jsx or Dashboard.jsx

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top