Published on

Implementing Protected Routes in React with React Router v6

32 views
5 min read
Authors

Implementing Protected Routes in React with React Router v6

Authentication and route protection are crucial aspects of modern web applications. In this guide, we'll walk through implementing protected routes in a React application using React Router v6. We'll create a system that redirects authenticated users away from the login page and protects private routes from unauthorized access.

Prerequisites

Before we begin, make sure you have:

  • A React project set up (preferably with Vite)
  • React Router v6 installed
  • Basic understanding of React and routing concepts

To install the required dependencies:

npm install react-router-dom

Understanding Protected Routes

Protected routes serve two main purposes:

  1. Preventing authenticated users from accessing login/signup pages
  2. Protecting private routes from unauthorized access

Let's implement both scenarios.

Step 1: Creating the Protected Route Component

First, let's create a reusable ProtectedRoute component that handles authentication checks:

// src/components/ProtectedRoute.jsx
import { Navigate, useLocation } from 'react-router-dom';

export const ProtectedRoute = ({ 
  isAuthenticated, 
  children, 
  redirectPath = '/',
  allowIfAuthenticated = false 
}) => {
  const location = useLocation();

  if (allowIfAuthenticated && !isAuthenticated) {
    // Redirect to login if trying to access a protected route while not authenticated
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (!allowIfAuthenticated && isAuthenticated) {
    // Redirect to home if trying to access login/signup while authenticated
    return <Navigate to={redirectPath} replace />;
  }

  return children;
};

Step 2: Setting Up Authentication Context

We'll create a simple authentication context to manage the user's authentication state:

// src/context/AuthContext.jsx
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => {
    // Implement your login logic here
    setIsAuthenticated(true);
  };

  const logout = () => {
    // Implement your logout logic here
    setIsAuthenticated(false);
  };

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

Step 3: Creating Route Components

Let's create some basic components for our routes:

// src/pages/Home.jsx
export const Home = () => {
  const { logout } = useAuth();
  
  return (
    <div className="p-4">
      <h1>Home Page</h1>
      <button 
        onClick={logout}
        className="bg-red-500 text-white px-4 py-2 rounded"
      >
        Logout
      </button>
    </div>
  );
};

// src/pages/Login.jsx
export const Login = () => {
  const { login } = useAuth();
  const location = useLocation();
  const navigate = useNavigate();

  const handleLogin = () => {
    login();
    const from = location.state?.from?.pathname || '/';
    navigate(from);
  };

  return (
    <div className="p-4">
      <h1>Login Page</h1>
      <button 
        onClick={handleLogin}
        className="bg-blue-500 text-white px-4 py-2 rounded"
      >
        Login
      </button>
    </div>
  );
};

// src/pages/Dashboard.jsx
export const Dashboard = () => (
  <div className="p-4">
    <h1>Dashboard (Protected Route)</h1>
  </div>
);

Step 4: Setting Up the Router

Now, let's put everything together in our main App component:

// src/App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import { ProtectedRoute } from './components/ProtectedRoute';
import { Home } from './pages/Home';
import { Login } from './pages/Login';
import { Dashboard } from './pages/Dashboard';

export default function App() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          {/* Public route */}
          <Route path="/" element={<Home />} />

          {/* Login route - protected from authenticated users */}
          <Route
            path="/login"
            element={
              <ProtectedRoute redirectPath="/" allowIfAuthenticated={false}>
                <Login />
              </ProtectedRoute>
            }
          />

          {/* Protected route - only for authenticated users */}
          <Route
            path="/dashboard"
            element={
              <ProtectedRoute redirectPath="/login" allowIfAuthenticated={true}>
                <Dashboard />
              </ProtectedRoute>
            }
          />
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

How It Works

  1. Authentication State: The AuthContext provides authentication state and methods throughout the app.
  2. Protected Route Component: Our ProtectedRoute component handles two scenarios:
    • Redirecting authenticated users away from login/signup pages
    • Protecting private routes from unauthorized access
  3. Route Configuration: In App.jsx, we:
    • Set up public routes normally
    • Protect the login route from authenticated users
    • Protect private routes from unauthenticated users
  4. Navigation: The Login component captures the original requested URL and redirects back after successful authentication.

Best Practices

  1. State Persistence: In a real application, you'd want to persist the authentication state:
// Enhanced AuthProvider
export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(() => {
    return localStorage.getItem('isAuthenticated') === 'true';
  });

  const login = () => {
    setIsAuthenticated(true);
    localStorage.setItem('isAuthenticated', 'true');
  };

  const logout = () => {
    setIsAuthenticated(false);
    localStorage.removeItem('isAuthenticated');
  };

  // ... rest of the provider
};
  1. Loading States: Add loading states for better UX:
const ProtectedRoute = ({ isAuthenticated, isLoading, children }) => {
  if (isLoading) {
    return <div>Loading...</div>;
  }
  
  // ... rest of the component
};

Conclusion

This implementation provides a robust foundation for handling protected routes in your React application. Remember to:

  • Implement proper authentication with a backend service
  • Add error handling and loading states
  • Consider using a token-based authentication system
  • Add proper TypeScript types if using TypeScript

The complete code for this tutorial is available on my GitHub repository [link]. Feel free to reach out if you have any questions!