Skip
Arish's avatar

21. Protected Routes


Protected Routes

Protected routes restrict access to certain pages based on authentication or authorization.

Basic Protected Route

jsx
1import { Navigate, useLocation } from 'react-router-dom'
2
3function ProtectedRoute({ children }) {
4  const { user, loading } = useAuth()
5  const location = useLocation()
6  
7  if (loading) {
8    return <LoadingSpinner />
9  }
10  
11  if (!user) {
12    // Redirect to login, preserving intended destination
13    return <Navigate to="/login" state={{ from: location }} replace />
14  }
15  
16  return children
17}
18
19// Usage
20<Routes>
21  <Route path="/login" element={<Login />} />
22  <Route
23    path="/dashboard"
24    element={
25      <ProtectedRoute>
26        <Dashboard />
27      </ProtectedRoute>
28    }
29  />
30</Routes>

Layout-Based Protection

jsx
1function ProtectedLayout() {
2  const { user, loading } = useAuth()
3  const location = useLocation()
4  
5  if (loading) {
6    return <LoadingSpinner />
7  }
8  
9  if (!user) {
10    return <Navigate to="/login" state={{ from: location }} replace />
11  }
12  
13  return (
14    <div>
15      <DashboardNav />
16      <Outlet />
17    </div>
18  )
19}
20
21// Routes
22<Routes>
23  {/* Public routes */}
24  <Route path="/" element={<Home />} />
25  <Route path="/login" element={<Login />} />
26  
27  {/* Protected routes */}
28  <Route element={<ProtectedLayout />}>
29    <Route path="/dashboard" element={<Dashboard />} />
30    <Route path="/settings" element={<Settings />} />
31    <Route path="/profile" element={<Profile />} />
32  </Route>
33</Routes>

Role-Based Access

jsx
1function RoleProtectedRoute({ children, allowedRoles }) {
2  const { user } = useAuth()
3  const location = useLocation()
4  
5  if (!user) {
6    return <Navigate to="/login" state={{ from: location }} replace />
7  }
8  
9  if (!allowedRoles.includes(user.role)) {
10    return <Navigate to="/unauthorized" replace />
11  }
12  
13  return children
14}
15
16// Usage
17<Routes>
18  <Route
19    path="/admin"
20    element={
21      <RoleProtectedRoute allowedRoles={['admin']}>
22        <AdminPanel />
23      </RoleProtectedRoute>
24    }
25  />
26  
27  <Route
28    path="/manager"
29    element={
30      <RoleProtectedRoute allowedRoles={['admin', 'manager']}>
31        <ManagerDashboard />
32      </RoleProtectedRoute>
33    }
34  />
35</Routes>

Redirect After Login

jsx
1function Login() {
2  const navigate = useNavigate()
3  const location = useLocation()
4  const { login } = useAuth()
5  
6  // Get the page user tried to visit before being redirected to login
7  const from = location.state?.from?.pathname || '/dashboard'
8  
9  const handleSubmit = async (e) => {
10    e.preventDefault()
11    try {
12      await login(email, password)
13      navigate(from, { replace: true })  // Go to intended page
14    } catch (error) {
15      setError('Login failed')
16    }
17  }
18  
19  return (
20    <form onSubmit={handleSubmit}>
21      {/* Login form */}
22    </form>
23  )
24}

Auth Context

jsx
1import { createContext, useContext, useState, useEffect } from 'react'
2
3const AuthContext = createContext(null)
4
5export function AuthProvider({ children }) {
6  const [user, setUser] = useState(null)
7  const [loading, setLoading] = useState(true)
8  
9  useEffect(() => {
10    // Check for existing session
11    const checkAuth = async () => {
12      try {
13        const token = localStorage.getItem('token')
14        if (token) {
15          const response = await fetch('/api/auth/me', {
16            headers: { Authorization: `Bearer ${token}` }
17          })
18          if (response.ok) {
19            const userData = await response.json()
20            setUser(userData)
21          }
22        }
23      } catch (error) {
24        console.error('Auth check failed:', error)
25      } finally {
26        setLoading(false)
27      }
28    }
29    
30    checkAuth()
31  }, [])
32  
33  const login = async (email, password) => {
34    const response = await fetch('/api/auth/login', {
35      method: 'POST',
36      headers: { 'Content-Type': 'application/json' },
37      body: JSON.stringify({ email, password })
38    })
39    
40    if (!response.ok) throw new Error('Login failed')
41    
42    const { user, token } = await response.json()
43    localStorage.setItem('token', token)
44    setUser(user)
45    return user
46  }
47  
48  const logout = () => {
49    localStorage.removeItem('token')
50    setUser(null)
51  }
52  
53  return (
54    <AuthContext.Provider value={{ user, loading, login, logout }}>
55      {children}
56    </AuthContext.Provider>
57  )
58}
59
60export function useAuth() {
61  const context = useContext(AuthContext)
62  if (!context) {
63    throw new Error('useAuth must be used within AuthProvider')
64  }
65  return context
66}

Complete Example

jsx
1// App.jsx
2import { BrowserRouter, Routes, Route } from 'react-router-dom'
3import { AuthProvider } from './contexts/AuthContext'
4
5function App() {
6  return (
7    <BrowserRouter>
8      <AuthProvider>
9        <Routes>
10          {/* Public */}
11          <Route path="/" element={<Home />} />
12          <Route path="/login" element={<Login />} />
13          <Route path="/register" element={<Register />} />
14          
15          {/* Protected */}
16          <Route element={<ProtectedLayout />}>
17            <Route path="/dashboard" element={<Dashboard />} />
18            <Route path="/profile" element={<Profile />} />
19            
20            {/* Admin only */}
21            <Route element={<AdminLayout />}>
22              <Route path="/admin" element={<AdminPanel />} />
23              <Route path="/admin/users" element={<UserManagement />} />
24            </Route>
25          </Route>
26          
27          {/* Error pages */}
28          <Route path="/unauthorized" element={<Unauthorized />} />
29          <Route path="*" element={<NotFound />} />
30        </Routes>
31      </AuthProvider>
32    </BrowserRouter>
33  )
34}
35
36// AdminLayout.jsx
37function AdminLayout() {
38  const { user } = useAuth()
39  
40  if (user?.role !== 'admin') {
41    return <Navigate to="/unauthorized" replace />
42  }
43  
44  return <Outlet />
45}

Protected routes keep your application secure!