Authentication With React Router V6: Step By Step Guide

In this guide, we are going to learn how to add react-router version 6 into a login process. React Router is a strong tool for managing paths in React applications and many people use it when working with React JS. Another routing library we have is named React Router.

React Router; Source: DEV Community

React Router is a compact and straightforward routing library for React that takes inspiration from React Router, Ember, and Preact Router. It is designed to be lightweight with support for basic route patterns only, and offers robust accessibility features that are currently in an experimental stage.

In this article, we will explore React Router version 6 and understand its functioning by combining the process of authentication with React Router v6.

Getting Started

Getting started; Source: YouTube

Create project setup

As a starting attempt, let us scaffold a react app utilizing Vite.

# npm 6.x

npm create vite@latest router-app --template react

# npm 7+, extra double-dash is needed:

npm create vite@latest router-app – --template react

Then, inside your project folder, you install the following dependency:

npm install react-router-dom --save

Now, please put this link inside your index.html to use the css framework and avoid handling classNames:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css"
    />
    <title>Vite App</title>
  </head>
  <!-- ... -->
</html>

Now that your project is set up and all needed dependencies are in place, you may go on to the following phase.

Create Generic Components

First let's create the Not Found page:

// @src/pages/NotFound.jsx
const NotFound = () => (
  <div>
    <h1>Not Found page</h1>
    <p>The page you tried to access doesn't exist.</p>
    <p>This is a generic route.</p>
  </div>
);
export default NotFound;

With your Not Found page created, you can proceed to create the Unauthorized page:

// @src/pages/Unauthorized.jsx
import { Link } from "react-router-dom";
const Unauthorized = () => (
  <div>
    <h1>Unauthorized page</h1>
    <p>You don't have permission to access this page.</p>
    <Link to="/login">Go back to login.</Link>
  </div>
);
export default Unauthorized;

You might have seen that we used the <Link /> component from React Router for navigating to different pages, like going to the login page here.

Next, you can focus on creating your Component called Layout. This component will include two main parts: the navigation bar and the <Link /> components for the pages to which you wish to navigate.

You will also see the <Outlet /> component, which has the job of showing all the child components, and these are your pages. It makes it possible to use the same layout across different pages.

// @src/components/Layout.jsx
import { Link, Outlet } from "react-router-dom";
const Layout = () => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/login">Login</Link>
      </li>
      <li>
        <Link to="/lounge">Lounge</Link>
      </li>
    </ul>
    <Outlet />
  </div>
);
export default Layout;

With the generic components created, you can move on to the next step.

Create Auth Context

Your authentication context will hold information about whether a user is logged in, and based on this you can decide if they are allowed to see specific pages.

The first step is to establish the context:

// @src/context/Auth.jsx
import { createContext } from "react";
const AuthContext = createContext(null);
// ...

Then you make a hook for using the context within React components.

// @src/context/Auth.jsx
import { createContext, useContext } from "react";
const AuthContext = createContext(null);
export const useAuth = () => useContext(AuthContext);
// ...

Now you can create your authentication provider:

// @src/context/Auth.jsx
import { createContext, useContext, useState } from "react";
const AuthContext = createContext(null);
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
};
// ...

Within the file that manages your authentication, you are able to construct a component whose job is to decide whether or not a user has permission to enter certain routes based on their current state of being authenticated.

If he does not have permission and tries to go into a secure area, he will be sent away to the Unauthorized page. If not that case, you can get into the paths without trouble.

// @src/context/Auth.jsx
import { createContext, useContext, useState } from "react";
import { useLocation, Navigate, Outlet } from "react-router-dom";
const AuthContext = createContext(null);
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
};
export const RequireAuth = () => {
  const { user } = useAuth();
  const location = useLocation();
  if (!user) {
    return (
      <Navigate
        to={{ pathname: "/unauthorized", state: { from: location } }}
        replace
      />
    );
  }
  return <Outlet />;
};

Having completed your authentication context, it is now time to proceed to the subsequent step.

Create App Pages

First of all, you need to create your main page:

// @src/pages/Home.jsx
const Home = () => {
  return (
    <div>
      <h1>Home page</h1>
      <p>This route has public access.</p>
    </div>
  );
};
export default Home;

Next, you can make your login page. Here the user must put in a username to get access to your application. After they submit, the user will go to a secure area.

// @src/pages/Login.jsx
import { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../context/Auth";
const Login = () => {
  const [username, setUsername] = useState("");
  const { setUser } = useAuth();
  const navigate = useNavigate();
  const login = useCallback(
    (e) => {
      e.preventDefault();
      setUser({ username });
      navigate("/lounge");
    },
    [setUser, username]
  );
  return (
    <div>
      <h1>Login page</h1>
      <p>This route has public access.</p>
      <form onSubmit={login}>
        <input
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          placeholder="Type username..."
        />
        <button type="submit">Login</button>
      </form>
    </div>
  );
};
export default Login;

After completing the Login page, it is necessary to establish a secure route. Additionally, on this same page, you must develop a function that offers users the possibility to sign out.

// @src/pages/Lounge.jsx
import { useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../context/Auth";
const Lounge = () => {
  const { user, setUser } = useAuth();
  const navigate = useNavigate();
  const logout = useCallback(
    (e) => {
      e.preventDefault();
      setUser(null);
      navigate("/");
    },
    [setUser]
  );
  return (
    <div>
      <h1>Lounge page</h1>
      <p>
        Hello <strong>{user?.username}</strong>!
      </p>
      <p>Looks like you have access to this private route!</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
};
export default Lounge;

With your application pages created, you can move on to the last step.

Define application routes

React router; Source: DhiWise

Before you start, you need to import all the necessary components:

// @src/App.jsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider, RequireAuth } from "./context/Auth";
import Layout from "./components/Layout";
import HomePage from "./pages/Home";
import LoginPage from "./pages/Login";
import NotFoundPage from "./pages/NotFound";
import LoungePage from "./pages/Lounge";
import UnauthorizedPage from "./pages/Unauthorized";
// ...

First, place your AuthProvider as the main component. After that, insert the <BrowserRouter /> component along with react router's <Routes /> underneath it as subordinate components.

// @src/App.jsx
// Hidden for simplicity
const App = () => {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          {/* ---------- */}
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
};
export default App;

Next you will define the Layout of your page using your <Layout /> component.

// @src/App.jsx
// Hidden for simplicity
const App = () => {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route element={<Layout />}>
            {/* ---------- */}
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
};
export default App;

Next, include the pages that a user can reach without needing to sign in, which covers pages for signing up and those displaying an error when something is not found.

// @src/App.jsx
// Hidden for simplicity
const App = () => {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route element={<Layout />}>
            <Route path="/" element={<HomePage />} />
            <Route path="/login" element={<LoginPage />} />
            <Route path="*" element={<NotFoundPage />} />
            <Route path="/unauthorized" element={<UnauthorizedPage />} />
            {/* ---------- */}
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
};
export default App;

Finally, you are now able to combine your secure pages with the element that decides whether a user has permission to enter these pathways.

// @src/App.jsx
// Hidden for simplicity
const App = () => {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route element={<Layout />}>
            <Route path="/" element={<HomePage />} />
            <Route path="/login" element={<LoginPage />} />

            <Route path="*" element={<NotFoundPage />} />
            <Route path="/unauthorized" element={<UnauthorizedPage />} />
            <Route element={<RequireAuth />}>
              <Route path="/lounge" element={<LoungePage />} />
            </Route>
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
};
export default App;

With everything set up, you can now do a little review.

When the user is not logged in, they should be able to see just the main page and the login page. If they attempt to go into the lounge page, which has protection, then they must be sent instead to a page saying they don't have permission. If the user goes to a page that isn't there in the application, it has to show the page that says it's not found.

However, if the user has logged in already, they can see all pages of the application but cannot go to a page that is not allowed while being logged into your app.

Conclusion

After you have learned how React-Router V6 operates by adding an authentication process, realized its function and found out the method to use it in your own React application, actually practicing is the best method to make sure you really understand.