Setting up protected routes with React Router v6

Protected routes are the easiest way to prevent unauthorized users from accessing certain pages

Published June 23, 2022

When should you use protected routes?

If you're setting up routing in your React application, there might be a few routes that you don't want unauthorized users to access. An example of this would be a /profile route for an application that requires credential authentication.

Without the implementation of protected routes, anyone can access the "Profile" page simply by typing the path into the browser's address bar. Ideally, the route should be inaccessible to users without authenticated credentials. If an unauthorized user tries to access the /profile route, they should be redirected back to the /login route. Luckily, this is super simple to accomplish, so let's dive right in.

Creating a protected route

For this guide, let's assume I'm creating an application with a "Login", "Home", "Profile", and "Settings" page. With basic routing set up, it should look something like this:


// App.js
import { AuthContextProvider } from "./contexts/auth-context";
import LogIn from "./pages/LogIn";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Settings from "./pages/Settings";
import { BrowserRouter, Routes, Route } from "react-router-dom";

const App = () => {
  return (
    <div>
      <AuthContextProvider>
        <BrowserRouter>
          <Routes>
            <Route path="login" element={<LogIn />} />
            <Route path="/" element={<Home />} />
            <Route path="profile" element={<Profile />} />
            <Route path="settings" element={<Settings />} />
          </Routes>
        </BrowserRouter>
      </AuthContextProvider>
    </div>
  );
};

export default App;
        

...each component has its own route, and every route is currently accessible to anyone. The AuthContextProvider serves as a wrapper component which holds the application's context, which isn't required or important for this guide — it just detects whether the user is logged in or not. My goal is to make the /, /profile, and /settings routes available only to authenticated users.

To do this, I need to create a wrapper component that'll hold the routes I want to protect. I'll name this component PrivateRoutes.js and set everything up:


// PrivateRoutes.js
import { useAuth } from "../contexts/auth-context";
import { Outlet, Navigate } from "react-router-dom";

const PrivateRoutes = () => {
  const { isLoggedIn } = useAuth();

  return isLoggedIn ? <Outlet /> : <Navigate to="login" />;
};

export default PrivateRoutes;
        

...useAuth is imported, which simply has the isLoggedIn state that holds a Boolean. After that, the Outlet and Navigate components are imported. The Outlet component is used in parent route elements to render child route elements. The Navigate component changes the current location of the application. The ternary operation inside PrivateRoutes basically says, "If the user is logged in, render all the child components inside the wrapper; if the user isn't logged in, redirect them to the 'Login' page if they try to access any of those child components."

Now that the rules for the protected route wrapper component are written, it can be imported and used:


// App.js
import { AuthContextProvider } from "./contexts/auth-context";
import LogIn from "./pages/LogIn";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Settings from "./pages/Settings";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import PrivateRoutes from "./components/PrivateRoutes";

const App = () => {
  return (
    <div>
      <AuthContextProvider>
        <BrowserRouter>
          <Routes>
            <Route path="login" element={<LogIn />} />

            <Route element={<PrivateRoutes />}>
              <Route path="/" element={<Home />} />
              <Route path="profile" element={<Profile />} />
              <Route path="settings" element={<Settings />} />
            </Route>
          </Routes>
        </BrowserRouter>
      </AuthContextProvider>
    </div>
  );
};

export default App;
        

...the PrivateRoutes component is used as a wrapper around the routes that require authentication. This works perfectly, but now there's an issue: even if the user is logged in, they can still access the /login path. Luckily, this can be solved by creating another wrapper component similar to the one I just created:


// DisableAuthenticateRoute.js
import { useAuth } from "../contexts/auth-context";
import { Outlet, Navigate } from "react-router-dom";

const DisableAuthenticateRoute = () => {
  const { isLoggedIn } = useAuth();

  return isLoggedIn ? <Navigate to="/" /> : <Outlet />;
};

export default DisableAuthenticateRoute;
        

...this time, the ternary operation says, "If the user is logged in, redirect them to the 'Home' page if they try to access the 'Login' page; if the user isn't logged in, render all the child components inside the wrapper."

With everything set up and complete, the final routing looks something like this:


// App.js
import { AuthContextProvider } from "./contexts/auth-context";
import LogIn from "./pages/LogIn";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
import Settings from "./pages/Settings";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import PrivateRoutes from "./components/PrivateRoutes";
import DisableAuthenticateRoute from "./components/DisableAuthenticateRoute";

const App = () => {
  return (
    <div>
      <AuthContextProvider>
        <BrowserRouter>
          <Routes>
            <Route element={<DisableAuthenticateRoute />}>
              <Route path="login" element={<LogIn />} />
            </Route>

            <Route element={<PrivateRoutes />}>
              <Route path="/" element={<Home />} />
              <Route path="profile" element={<Profile />} />
              <Route path="settings" element={<Settings />} />
            </Route>
          </Routes>
        </BrowserRouter>
      </AuthContextProvider>
    </div>
  );
};

export default App;
        

...all done! Unauthorized users will now be redirected to the /login path until they log in, and authorized users won't be able to access that path unless they log out.