Protected Routes and Authentication

Introduction

In modern React applications, certain pages or features should be accessible only to authenticated users. Examples include user dashboards, profile pages, or admin panels. Protected routes ensure that unauthorized users cannot access sensitive content by checking their authentication status before rendering a component.

React Router, combined with React’s state management, makes it straightforward to implement protected routes. In this post, we will explore:

  • What protected routes are
  • How to restrict access based on authentication state
  • Example of a private dashboard accessible only to logged-in users
  • Redirecting unauthenticated users to a login page

What Are Protected Routes?

Protected routes are routes that require specific conditions to be met before allowing access. Typically, this condition is whether the user is authenticated.

  • Public routes: Accessible by anyone (e.g., login, registration, home page).
  • Private routes: Accessible only by logged-in users.
  • Role-based routes: Accessible by users with specific roles (e.g., admin, editor).

Why Use Protected Routes?

  1. Security: Prevent unauthorized users from accessing sensitive data.
  2. User experience: Guide users to login pages if they try to access protected content.
  3. Code maintainability: Centralized access control logic simplifies large apps.

Restricting Access Based on Authentication State

Authentication state can be managed in React state, Context API, or a global state manager like Redux. A simple approach is using a state variable like isAuthenticated:

import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Login from './Login';
import Dashboard from './Dashboard';

function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  return (
<BrowserRouter>
  <Routes>
    <Route path="/login" element={<Login setIsAuthenticated={setIsAuthenticated} />} />
    <Route
      path="/dashboard"
      element={isAuthenticated ? <Dashboard /> : <Navigate to="/login" />}
    />
  </Routes>
</BrowserRouter>
); } export default App;

Explanation:

  • isAuthenticated determines whether the user is logged in.
  • If a user tries to access /dashboard without authentication, they are redirected to /login using the Navigate component.
  • setIsAuthenticated is passed to the login component to update state upon successful login.

Creating a PrivateRoute Component

To simplify protected routes, you can create a reusable component called PrivateRoute.

import React from 'react';
import { Navigate } from 'react-router-dom';

function PrivateRoute({ isAuthenticated, children }) {
  return isAuthenticated ? children : <Navigate to="/login" />;
}

export default PrivateRoute;

Usage Example:

<Routes>
  <Route path="/login" element={<Login setIsAuthenticated={setIsAuthenticated} />} />
  <Route
path="/dashboard"
element={
  &lt;PrivateRoute isAuthenticated={isAuthenticated}&gt;
    &lt;Dashboard /&gt;
  &lt;/PrivateRoute&gt;
}
/> </Routes>

Benefits:

  • Centralizes access control logic.
  • Avoids repeating Navigate logic in multiple routes.
  • Makes the app more maintainable.

Example: Private Dashboard Accessible Only to Logged-In Users

Step 1: Authentication State

import React, { useState } from 'react';

function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  return (
&lt;BrowserRouter&gt;
  &lt;Routes&gt;
    &lt;Route path="/login" element={&lt;Login setIsAuthenticated={setIsAuthenticated} /&gt;} /&gt;
    &lt;Route
      path="/dashboard"
      element={
        &lt;PrivateRoute isAuthenticated={isAuthenticated}&gt;
          &lt;Dashboard /&gt;
        &lt;/PrivateRoute&gt;
      }
    /&gt;
  &lt;/Routes&gt;
&lt;/BrowserRouter&gt;
); }

Step 2: Login Component

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

function Login({ setIsAuthenticated }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const navigate = useNavigate();

  const handleLogin = (e) => {
e.preventDefault();
// Simple authentication check (replace with real API)
if (username === 'admin' &amp;&amp; password === 'password') {
  setIsAuthenticated(true);
  navigate('/dashboard'); // Redirect after login
} else {
  alert('Invalid credentials');
}
}; return (
&lt;form onSubmit={handleLogin}&gt;
  &lt;h2&gt;Login&lt;/h2&gt;
  &lt;input
    type="text"
    placeholder="Username"
    value={username}
    onChange={(e) =&gt; setUsername(e.target.value)}
  /&gt;
  &lt;input
    type="password"
    placeholder="Password"
    value={password}
    onChange={(e) =&gt; setPassword(e.target.value)}
  /&gt;
  &lt;button type="submit"&gt;Login&lt;/button&gt;
&lt;/form&gt;
); } export default Login;

Explanation:

  • useNavigate redirects users to /dashboard upon successful login.
  • setIsAuthenticated(true) updates the authentication state.
  • Invalid credentials show an alert (in real apps, use proper API responses).

Step 3: Dashboard Component

import React from 'react';

function Dashboard() {
  return (
&lt;div&gt;
  &lt;h1&gt;Private Dashboard&lt;/h1&gt;
  &lt;p&gt;Welcome to your dashboard! Only logged-in users can see this.&lt;/p&gt;
&lt;/div&gt;
); } export default Dashboard;

Redirecting Unauthenticated Users to Login Page

Using the Navigate component, you can redirect unauthenticated users anywhere:

<Route
  path="/dashboard"
  element={isAuthenticated ? <Dashboard /> : <Navigate to="/login" />}
/>

Explanation:

  • When isAuthenticated is false, the user is automatically sent to the login page.
  • This ensures sensitive pages are never rendered for unauthorized users.

Role-Based Protected Routes (Advanced)

Some applications require role-based access (e.g., admin vs regular user).

function RoleProtectedRoute({ isAuthenticated, userRole, allowedRoles, children }) {
  if (!isAuthenticated) {
return &lt;Navigate to="/login" /&gt;;
} if (!allowedRoles.includes(userRole)) {
return &lt;Navigate to="/unauthorized" /&gt;;
} return children; }

Usage Example:

<Route
  path="/admin"
  element={
&lt;RoleProtectedRoute
  isAuthenticated={isAuthenticated}
  userRole={currentUser.role}
  allowedRoles={&#91;'admin']}
&gt;
  &lt;AdminDashboard /&gt;
&lt;/RoleProtectedRoute&gt;
} />
  • Ensures that only users with the proper role can access the route.
  • Redirects unauthorized users to an unauthorized page or another route.

Protecting Nested Routes

React Router supports nested routes for complex dashboards. Protected routes can wrap the parent route to secure all child routes.

<Route
  path="/dashboard"
  element={
&lt;PrivateRoute isAuthenticated={isAuthenticated}&gt;
  &lt;DashboardLayout /&gt;
&lt;/PrivateRoute&gt;
} > <Route path="profile" element={<Profile />} /> <Route path="settings" element={<Settings />} /> </Route>
  • All nested routes (/dashboard/profile, /dashboard/settings) are automatically protected.
  • DashboardLayout renders an <Outlet /> for child routes.

Storing Authentication State

For real applications, authentication state is often stored in:

  • Local Storage or Session Storage: Persist state across reloads.
  • Cookies: For secure tokens.
  • Context API or Redux: Share authentication state across components.

Example using local storage:

const [isAuthenticated, setIsAuthenticated] = useState(
  !!localStorage.getItem('token')
);

const handleLogin = () => {
  localStorage.setItem('token', 'your-token');
  setIsAuthenticated(true);
};

const handleLogout = () => {
  localStorage.removeItem('token');
  setIsAuthenticated(false);
};
  • Ensures the user stays logged in after refreshing the page.

Comments

Leave a Reply

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