Create a React project

 Using the command below will create a project for us

$ npm create vite@latest react-jwt-cn


We then select react and javascript as our framework and language. Before we start the project, we want to make sure that all of our dependencies are installed, so we’ll start by running

$ npm install


After installation, in the root directory of our project, we can run the following command to start our project

$ npm run dev


Let’s go through these steps to get our React project up and running smoothly

 Installing React-Router and Axios


Before we continue, make sure we have installed the necessary dependencies for our project. We’ll start by installing react-router v6, which will handle routing in our React application. Additionally, we’ll install Axios, a library for sending API requests. By performing these steps, we will be equipped with the tools we need to implement seamless routing and perform efficient API communication. Let’s start by installing these dependencies.

$ npm install react-router-dom axios


Creating AuthProvider and AuthContext in React


The next thing we are going to implement is the JWT authentication functionality. In this subsection we will create a AuthProvider component and an associated AuthContext . This will assist us in storing and sharing JWT authentication related data and functions throughout the application


Create authProvider.js under src > provider . Then we will explore the implementations of AuthProvider and AuthContext .

  1.  Import the necessary modules and dependency packages:


    1. Import axios for sending API requests

    2. Import from react createContext useContext useEffect useMemo and useState
import axios from "axios";
import {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";

  1. Use createContext() to create a context for authentication


    1. createContext() The empty context created is used to share authenticated data and functions between components of the
const AuthContext = createContext();
  1.  Creating the AuthProvider Component


    1. This component is used as the authentication context for the provider

    2. It receives children as props, representing the child components that will have access to the authentication context.
const AuthProvider = ({ children }) => {

};

  1. Define a state named token using useState

    1.   token This represents an authentication token.

    2. If the token data exists, we will get it via localStorage.getItem("token")
const [token, setToken_] = useState(localStorage.getItem("token"));

  1. Create setToken function to update token data for authentication

    1.  This function will be used to update the authentication token

    2. It uses the setToken_ function to update the token data and stores the updated data in the local environment at localStorage.setItem() .
const setToken = (newToken) => {
  setToken_(newToken);
};

  1. Use useEffect() to set the axios default authentication request header and save the authentication token data locally


    1. Every time token is updated, the effect function executes the

    2. If token exists, it will be set as the request header for axios and saved to the local localStorage

    3. If token is null or undefined, it removes the corresponding axios request header and the localStorage data associated with local authentication.
useEffect(() => {
  if (token) {
    axios.defaults.headers.common["Authorization"] = "Bearer " + token;
    localStorage.setItem('token',token);
  } else {
    delete axios.defaults.headers.common["Authorization"];
    localStorage.removeItem('token')
  }
}, [token]);
  1.  Creating Memorized Contexts with useMemo


    1. This context contains the token and setToken functions

    2. The value of the token is used as a dependency for memorization (if the token does not change, it is not re-rendered)
const contextValue = useMemo(
  () => ({
    token,
    setToken,
  }),
  [token]
);
  1.  Injecting authentication contexts into self-components

    1.  Wrapping subcomponents using AuthContext.Provider

    2. Pass the contextValue as the value of the provider.
return (
  <AuthContext.Provider value={contextValue}>
    {children}
  </AuthContext.Provider>
);

  1. Export the useAuth hook for external use in the authentication context.


    1. useAuth is a customized hook that allows subcomponents to easily access authentication information
export const useAuth = () => {
  return useContext(AuthContext);
};
  1.  Default Export AuthProvider
export default AuthProvider;

 Full Code

import axios from "axios";
import {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";

const AuthContext = createContext();

const AuthProvider = ({ children }) => {

    const [token, setToken_] = useState(localStorage.getItem("token"));

    const setToken = (newToken) => {
        setToken_(newToken);
    };

    useEffect(() => {
        if (token) {
          axios.defaults.headers.common["Authorization"] = "Bearer " + token;
          localStorage.setItem('token',token);
        } else {
          delete axios.defaults.headers.common["Authorization"];
          localStorage.removeItem('token')
        }
    }, [token]);
    
    const contextValue = useMemo(
        () => ({
          token,
          setToken,
        }),
        [token]
    );

    return (
        <AuthContext.Provider value={contextValue}>
          {children}
        </AuthContext.Provider>
    );

};

export const useAuth = () => {
    return useContext(AuthContext);
};
  
export default AuthProvider;


To summarize, this code uses React’s context API to set the authentication context. It provides authentication tokens and setToken functions to subcomponents through context. It also ensures that the default authorization request header in axios is updated when the authentication token is updated.

 Creating a Route for JWT Authentication


In order to be able to organize routing more efficiently, we will create a src > routes directory. Within this directory, we will create a index.jsx file, which is used as an entry point for defining routes for the entire application. By building our routes in separate folders, we can maintain a clear and manageable routing structure. Let’s continue creating routes and explore how we can integrate JWT authentication into our React application.

 Creating Protected Route Components for Authentication Routes


To protect our authenticated routes and prevent unauthorized access, we will create a component called ProtectedRoute . This component will wrap our authentication routes to ensure that only authorized users have access. By implementing this component, we can easily accomplish our authentication needs and provide a great user experience. We will create the ProtectedRoute.jsx file under src > routes


  1. First we have to import the necessary dependencies from react-router-dom
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";

  1. Define the ProtectedRoute component and let it wrap all our routes that need authentication
export const ProtectedRoute = () => {
    const { token } = useAuth();

    if (!token) {

      return <Navigate to="/login" />;
    }

    return <Outlet />;
 };

  1. In the ProtectedRoute component, we use a custom hook (useAuth) provided by AuthContext to get the token information

  2. Next we check if the token exists. If the user is not authorized (token is faslse or null), we will navigate the route to the login page ( /login )

  3. If the user is authorized, we will use the Outlet component to render the child route.The Outlet component acts as a placeholder to display the child components defined in the parent route.


To summarize, the ProtectedRoute component acts as a guard for authenticated routes. If the user fails authentication, they will be redirected to the login page. If the user is authenticated, the subroutes defined in the ProtectedRoute component will be rendered using the Outlet component.


The above code allows us to easily secure specific routes and control access based on a user’s authentication status, thus providing a secure navigation experience in our React apps.

 Explore Routing in Depth


Now that we have the ProtectedRoute component and the authentication context, we can move on to defining our routes. By distinguishing between public routes, checksum-protected routes, and non-authenticated user routes, we can effectively handle navigation and access control based on JWT authentication. Next we will dive into the src > routes > index.jsx file and explore how we can integrate JWT authentication into our routing structure!

  1.  Importing necessary dependencies


    1. RouterProvider and createBrowserRouter are used to configure and provide routing capabilities
    2.   useAuth Run the context in which we access the identity verification
    3.   ProtectedRoute Component wrapped around checksum routes
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
  1.  Defining Routing Components

    1.  This function component acts as an entry point for configuring application routing
const Routes = () => {
  const { token } = useAuth();

};

  1. Using the useAuth hook to access the authentication token


    1. Call the useAuth hook to get a token from the authentication context.
const { token } = useAuth();
  1.  Define routes for all users (public routes)


    1. routesForPublic The array protects all routing information that is accessible to all users. Each routing information object contains a path and an element

    2. The path attribute specifies the URL path of the route, and the element attribute points to the jsx component/element that needs to be rendered under that route.
const routesForPublic = [
  {
    path: "/service",
    element: <div>Service Page</div>,
  },
  {
    path: "/about-us",
    element: <div>About Us</div>,
  },
];
  1.  Define routes that only authorized users can access


    1. routesForAuthenticatedOnly The array contains route objects that can only be accessed by authenticated users. It includes the protected root route (“/”) wrapped in the ProtectedRoute component and other child routes defined using the children attribute.
const routesForAuthenticatedOnly = [
  {
    path: "/",
    element: <ProtectedRoute />,
    children: [
      {
        path: "/",
        element: <div>User Home Page</div>,
      },
      {
        path: "/profile",
        element: <div>User Profile</div>,
      },
      {
        path: "/logout",
        element: <div>Logout</div>,
      },
    ],
  },
];
  1.  Define routes that can only be accessed by unauthorized users


    1. routesForNotAuthenticatedOnly The array contains routing objects accessed by unauthenticated users. It contains the login route ( /login )
const routesForNotAuthenticatedOnly = [
  {
    path: "/",
    element: <div>Home Page</div>,
  },
  {
    path: "/login",
    element: <div>Login</div>,
  },
];
  1.  Combine and determine routes based on authentication state


    1. The createBrowserRouter function is used to create a routing configuration, which takes an array of routes as an input.

    2. The extension operator (…) is used to combine multiple routing arrays into a single array

    3. The conditional expression ( !token ? routesForNotAuthenticatedOnly : [] ) checks if the user is authenticated (token present). If not, it contains the routesForNotAuthenticatedOnly array; otherwise, it contains an empty array.
const router = createBrowserRouter([
  ...routesForPublic,
  ...(!token ? routesForNotAuthenticatedOnly : []),
  ...routesForAuthenticatedOnly,
]);

  1. Injecting Routing Configurations with RouterProvider


    1. The RouterProvider component wraps the routing configuration so that it can be used throughout the application
return <RouterProvider router={router} />;

 Full Code

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";

const Routes = () => {
  const { token } = useAuth();

  const routesForPublic = [
    {
      path: "/service",
      element: <div>Service Page</div>,
    },
    {
      path: "/about-us",
      element: <div>About Us</div>,
    },
  ];


  const routesForAuthenticatedOnly = [
    {
      path: "/",
      element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
      children: [
        {
          path: "/",
          element: <div>User Home Page</div>,
        },
        {
          path: "/profile",
          element: <div>User Profile</div>,
        },
        {
          path: "/logout",
          element: <div>Logout</div>,
        },
      ],
    },
  ];


  const routesForNotAuthenticatedOnly = [
    {
      path: "/",
      element: <div>Home Page</div>,
    },
    {
      path: "/login",
      element: <div>Login</div>,
    },
  ];

  const router = createBrowserRouter([
    ...routesForPublic,
    ...(!token ? routesForNotAuthenticatedOnly : []),
    ...routesForAuthenticatedOnly,
  ]);

  return <RouterProvider router={router} />;
};

export default Routes;

 final integration


Now we have AuthContext , AuthProvider and Routes ready. Let’s integrate them into the App.jsx

  1.  Importing the necessary components and files


    1. AuthProvider is a component imported from the ./provider/authProvider file. It provides the authentication context for the entire application

    2. Import Routes from ./routes . It defines the application routing
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";

  1. Use the AuthProvider component to wrap the Routes component


    1. AuthProvider component is used to provide an authentication context to the application. It wraps the Routes component so that the authentication context can be used for all components in the Routes component tree
return (
  <AuthProvider>
    <Routes />
  </AuthProvider>
);

 Full Code

import AuthProvider from "./provider/authProvider";
import Routes from "./routes";

function App() {
  return (
    <AuthProvider>
      <Routes />
    </AuthProvider>
  );
}

export default App;

 Realization of login and logout

 Create a login page at src > pages > Login.jsx

const Login = () => {
  const { setToken } = useAuth();
  const navigate = useNavigate();

  const handleLogin = () => {
    setToken("this is a test token");
    navigate("/", { replace: true });
  };

  setTimeout(() => {
    handleLogin();
  }, 3 * 1000);

  return <>Login Page</>;
};

export default Login;
  •  The login component is a function component used to represent the login page

  • Use the useAuth hook to import the setToken function from the authentication context

  • Import the navigate function from react-router-dom for handling route jumps

  • Inside the component, there is a handleLogin function that sets the test token using the setToken function in the context and navigates to the home page (“/”) with the replace option set to true

  • The setTimeout function is used to simulate a 3-second delay before executing the handleLogin function

  • The component returns JSX for the login page, which acts as a placeholder text in this case


Now, let’s create a logout page at src > pages > Logout.jsx

import { useNavigate } from "react-router-dom";
import { useAuth } from "../provider/authProvider";

const Logout = () => {
  const { setToken } = useAuth();
  const navigate = useNavigate();

  const handleLogout = () => {
    setToken();
    navigate("/", { replace: true });
  };

  setTimeout(() => {
    handleLogout();
  }, 3 * 1000);

  return <>Logout Page</>;
};

export default Logout;

  • On the logout page, we call the setToken function without passing a parameter, which is equivalent to calling the setToken(null)


Now, we will replace the login and logout components of the routing component with updated versions of the

const routesForNotAuthenticatedOnly = [
  {
    path: "/",
    element: <div>Home Page</div>,
  },
  {
    path: "/login",
    element: <Login />,
  },
];


In the routesForNotAuthenticatedOnly array, the element property of “/login” is set to <Login /> to indicate that when the user accesses the “/login” path, the Login component is rendered

const routesForAuthenticatedOnly = [
  {
    path: "/",
    element: <ProtectedRoute />,
    children: [
      {
        path: "/",
        element: <div>User Home Page</div>,
      },
      {
        path: "/profile",
        element: <div>User Profile</div>,
      },
      {
        path: "/logout",
        element: <Logout />,
      },
    ],
  },
];


In the routesForAuthenticatedOnly array, the element property of “/logout” is set to <Logout /> to indicate that when the user accesses the “/logout” path, the Logout component is rendered

 Testing process


  1. When you first visit the root page / , you will see ” Home page ” in the routesForNotAuthenticatedOnly array.

  2. If you navigate to /login , after a delay of 3 seconds, the login process will be simulated. It will set the test token using the setToken function in the authentication context and then you will be redirected to the root page / by the navigation function in the react-router-dom library. After the redirection, you will see “User Home Page” from the routesForAuthenticatedOnly array

  3. If you then visit /logout , after a 3 second delay, the logout process will be simulated. It will clear the authentication token by calling the setToken function without any parameters and then you will be redirected to the root page / . Since you are now logged out, we will see ” Home Page” from the routesForNotAuthenticatedOnly array.


This flow demonstrates the login and logout process, where the user transitions between authenticated and unauthenticated states, and the corresponding routes are displayed accordingly.

By hbb

Leave a Reply

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