Handbook
Auth & Protected Routes

Authentication & Protecting Routes using React Router 6

Last updated by Noel Varanda (opens in a new tab),
React Router
Authentication
Protected Routes
💡

The following is based on Robin Weiruch's post on React Router 6: Authentication (opens in a new tab).

When building a web application, it is often necessary to restrict access to certain routes and allow access only to authenticated users. In this article, you'll learn how to setup authentication and protected routes using react router. We'll cover the following APIs:

Preview

Below is a preview of the output of the code. Have a look at all the code here (opens in a new tab).

The code

Auth provider

You'll need to create an <AuthProvider /> for your application. The following doesn't do authentication, but sets the architecture necessary for implementing it with routing. Visit Authentication to understand how to hook this into your auth mechanism.

src/domains/auth/providers/auth.tsx
import * as React from 'react';
import { mockAuthService, User } from '../services/mockAuthService';
 
const AuthContext = React.createContext({
  user: null,
  login: () => {},
  logout: () => {},
});
 
export const AuthProvider = ({ children }) => {
  const [user, setUser] = React.useState<User | null>(null);
 
  const login = async () => {
    const user = await mockAuthService.login();
    setUser(user);
  };
 
  const logout = () => setUser(null);
 
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};
 
export const useAuth = () => React.useContext(AuthContext);

Component guards

You'll need to create 3 guards. These will be in charge of performing checks, and if those checks don't pass; redirecting the user. Use the code below to create a generic <ProtectedRoute /> component.

src/router/components/ProtectedRoute.tsx
import * as React from 'react';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
 
export type ProtectedRouteProps = { children?: React.ReactElement } & {
  isAllowed: boolean;
  redirectPath?: string;
};
 
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
  isAllowed,
  children,
  redirectPath = '/',
}) => {
  const location = useLocation();
 
  if (!isAllowed) {
    // Redirect them to the /login page, but save the current location they were
    // trying to go to when they were redirected. This allows us to send them
    // along to that page after they login, which is a nicer user experience
    // than dropping them off on the home page.
    return <Navigate to={redirectPath} state={{ from: location }} replace />;
  }
 
  // Children is used when the ProtectedRoute is not used as Layout component
  // eslint-disable-next-line react/jsx-no-useless-fragment
  return children ?? <Outlet />;
};

Then add an <AuthenticationGuard /> component which extends this but is specific to whether a user is authenticated.

src/router/components/AuthenticationGuard.tsx
import * as React from 'react';
import { useAuth } from '../../domains/auth/providers/auth';
import { ProtectedRoute } from './ProtectedRoute';
 
export type AuthenticationGuardProps = {
  children?: React.ReactElement;
  redirectPath?: string;
  guardType?: 'authenticated' | 'unauthenticated';
};
 
export const AuthenticationGuard: React.FC<AuthenticationGuardProps> = ({
  redirectPath = '/login',
  guardType = 'authenticated',
  ...props
}) => {
  const { user } = useAuth();
  const isAllowed = guardType === 'authenticated' ? !!user : !user;
 
  return (
    <ProtectedRoute
      redirectPath={redirectPath}
      isAllowed={isAllowed}
      {...props}
    />
  );
};

Create a router

Lastly, any routes you need protected with these guards:

src/router/router.tsx
import * as React from 'react';
import {
  createBrowserRouter,
  createRoutesFromElements,
  Route,
  useLocation,
  useNavigate,
} from 'react-router-dom';
import { useAuth } from '../domains/auth/providers/auth';
import { Layout } from '../layouts/Layout';
import { AuthenticationGuard } from './components/AuthenticationGuard';
 
const HomePage = () => <div>Home</div>;
const SettingsPage = () => <div>Settings</div>;
 
const LoginPage = () => {
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();
  const from = location.state?.from?.pathname || '/';
 
  const handleLogin = async () => {
    await login();
    // Send them back to the page they tried to visit when they were
    // redirected to the login page. Use { replace: true } so we don't create
    // another entry in the history stack for the login page.  This means that
    // when they get to the protected page and click the back button, they
    // won't end up back on the login page, which is also really nice for the
    // user experience.
    navigate(from, { replace: true });
  };
 
  return (
    <div>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};
 
const LogoutPage = () => {
  const { user, logout } = useAuth();
 
  React.useEffect(() => {
    if (user) logout();
  }, [user, logout]);
 
  return null;
};
 
const routes = createRoutesFromElements(
  <Route element={<Layout />}>
    <Route index element={<HomePage />} />
 
    {/* Protect route based on authentication */}
    <Route element={<AuthenticationGuard />}>
      <Route path="settings" element={<SettingsPage />} />
      <Route path="logout" element={<LogoutPage />} />
    </Route>
 
    {/* Login page in case unauthenticated */}
    <Route element={<AuthenticationGuard guardType="unauthenticated" />}>
      <Route path="login" element={<LoginPage />} />
    </Route>
  </Route>
);
 
export const router = createBrowserRouter(routes);

Add a layout

One more thing! Don't forget to create the Layout.tsx:

src/layouts/Layout.tsx
import * as React from 'react';
import { Link, Outlet } from 'react-router-dom';
import { useAuth } from '../domains/auth/providers/auth';
 
export const Layout = () => {
  const { user, onLogout } = useAuth();
 
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/settings">Settings</Link>
          </li>
          <li>
            {!user ? (
              <Link to="/login">Login</Link>
            ) : (
              <Link to="/logout">Logout</Link>
            )}
          </li>
        </ul>
      </nav>
      <Outlet />
    </div>
  );
};

Create the app

Finally, tie it all together in your App.tsx:

src/App.tsx
import { RouterProvider } from 'react-router-dom';
import * as React from 'react';
import { AuthProvider } from './domains/auth/providers/auth';
import { router } from './router/router';
 
const App = () => (
  <AuthProvider>
    <RouterProvider router={router} />
  </AuthProvider>
);
 
export default App;

Keep up to date with any latest changes or announcements by subscribing to the newsletter below.