In this article, we will explore the seamless integration of JWT authentication with React and React-router. We’ll also learn how to handle public routes, validation-protected routes, and how to leverage the axios library to make API requests with authentication tokens.
Create a React project
Using the command below will create a project for us
npm create vite@latest react-jwt-cn
Then we choose react
and javascript
as our framework and language. Before starting the project, we need to ensure that all dependencies have been installed, so we need to execute
npm install
After installation, in the root directory of the project, we can run the following command to start our project
npm run dev
We go through these steps to get our React project up and running smoothly
Install React-Router and Axios
Before we continue, let’s 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 will install Axios, a library for sending API requests. By following these steps, we will be equipped with the tools needed to achieve seamless routing and perform efficient API communication. Let’s start by installing these dependencies.
npm install react-router-dom axios
Create AuthProvider and AuthContext
in React
The next thing we need to implement is the JWT authentication function. In this section we will create an AuthProvider
component and an associated AuthContext
. This will help us store and share JWT authentication related data and functions throughout the application
Create authProvider.js
under src > provider
. Then let’s explore the implementation of AuthProvider
and AuthContext
-
Import necessary modules and dependencies:
- Import
axios
for sending API requests - Import
createContext
useContext
useEffect
useMemo
anduseState
fromreact
>
- Import
import axios from "axios"; import { createContext, useContext, useEffect, useMemo, useState, } from "react";
-
Use
createContext()
to create a context for authentication- The empty context created by
createContext()
is used to share authentication data and functions between components.
- The empty context created by
const AuthContext = createContext();
2. Create the AuthProvider component
- This component is used as the provider of the authentication context
- It receives children as prop, representing the child components that will have access to the authentication context.
const AuthProvider = ({ children }) => { //Write component content here };
3. Use useState
to define a state named token
token
represents the authentication token- If the token data exists, we will get it through
localStorage.getItem("token")
const [token, setToken_] = useState(localStorage.getItem("token"));
4. Create the setToken
function to update the authentication token data
- This function will be used to update the authentication token
- It uses the
setToken_
function to update the token data and stores the updated data in the local environment throughlocalStorage.setItem()
const setToken = (newToken) => { setToken_(newToken); };
5. Use useEffect()
to set the default authentication request header of axios and save the authentication token data locally.
- Whenever
token
is updated, this effect function will be executed - If
token
exists, it will be set as axios request header and saved to localStorage - If
token
is null or undefined, it will remove the corresponding axios request header and localStorage data related to 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]);
6. Use useMemo
to create memorized contexts
- This context contains the
token
andsetToken
functions - The value of the token will be used as a memoized dependency (if the token does not change, it will not be re-rendered)
const contextValue = useMemo( () => ({ token, setToken, }), [token] );
7. Inject authentication context into self-component
- Use
AuthContext.Provider
to wrap child components - Pass contextValue as the value of provider
return ( <AuthContext.Provider value={contextValue}> {children} </AuthContext.Provider> );
8. Export the useAuth hook for external use in the authentication context.
- useAuth is a custom hook that allows sub-components to easily access authentication information
export const useAuth = () => { return useContext(AuthContext); };
9. Export AuthProvider by default
export default AuthProvider;
Complete 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 the authentication token and setToken function to the child component through the context. It also ensures that the default authorization request headers in axios are updated promptly when the authentication token is updated.
Create a route for JWT authentication
In order to organize routes more efficiently, we will create a src > routes
directory. In this directory, we will create an index.jsx
file, which will be used as the entry point for defining the routing of 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 to integrate JWT authentication into our React application.
Create a protected routing component for authentication routing
To protect our authenticated route and prevent unauthorized access, we will create a component called ProtectedRoute
. This component will wrap our authentication route to ensure that only authorized users have access. By implementing this component, we can easily complete the authentication needs and provide a good user experience. We will create the ProtectedRoute.jsx
file under src > routes
- First we need to import the necessary dependencies from
react-router-dom
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../provider/authProvider";
2. Define the ProtectedRoute
component and let it wrap all our routes that require authentication
export const ProtectedRoute = () => { const { token } = useAuth(); // Determine whether the user has permissions if (!token) { //If there is no authorization, jump to the login page return <Navigate to="/login" />; } // If authorized, render the subcomponent directly return <Outlet />; };
3. In the ProtectedRoute
component, we obtain token information through the custom hook (useAuth) provided by AuthContext
4. Next we check whether the token exists. If the user is not authorized ( token is faslse or null ), we will route to the login page (/login
)
5. If the user is authorized, we will use the Outlet component to render the child route. The Outlet component acts as a placeholder, displaying the child components defined in the parent route.
In summary, the ProtectedRoute
component acts as a guard for authenticated routes. If the user is not authenticated, they will be redirected to the login page. If the user is authenticated, the child routes 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 the user’s authentication status, providing a secure navigation experience in our React application.
Explore routing in depth
Now that we have the ProtectedRoute
component and the authentication context, we can move on to defining our route. By distinguishing between public routes, validation-protected routes, and non-authenticated user routes, we can efficiently handle JWT authentication-based navigation and access control. Next we’ll dive into the src > routes > index.jsx
file and explore how to integrate JWT authentication into our routing structure
-
Import necessary dependencies
RouterProvider
andcreateBrowserRouter
are used to configure and provide routing functionsuseAuth
runs the context in which we access authentication- The
ProtectedRoute
component wraps the verified route
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { useAuth } from "../provider/authProvider"; import { ProtectedRoute } from "./ProtectedRoute";
- Define routing components
- This function component serves as the entry point for configuring application routing
const Routes = () => { const { token } = useAuth(); //Write routing configuration here };
-
Use useAuth hook to access authentication token
- Call useAuth hook to obtain the token from the authentication context
const { token } = useAuth();
-
Define routes for all users (public routes)
- The
routesForPublic
array protects all routing information that is accessible to all users. Each routing information object contains a path and an element - 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 the route.
- The
const routesForPublic = [ { path: "/service", element: <div>Service Page</div>, }, { path: "/about-us", element: <div>About Us</div>, }, ];
-
Define routes that only authorized users can access
- The
routesForAuthenticatedOnly
array contains route objects that can only be accessed by authenticated users. It includes the protected root route (“/”) wrapped in a ProtectedRoute component and additional child routes defined using the children attribute.
- The
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>, }, ], }, ];
-
Define routes that only unauthorized users can access
- The
routesForNotAuthenticatedOnly
array contains route objects that are not accessible to authenticated users. It contains the login route (/login
)
- The
const routesForNotAuthenticatedOnly = [ { path: "/", element: <div>Home Page</div>, }, { path: "/login", element: <div>Login</div>, }, ];
Compose and determine routes based on authentication status
- The createBrowserRouter function is used to create routing configuration. It receives a routing array as an input parameter.
- The spread operator (…) is used to combine multiple route arrays into a single array
- The conditional expression (
!token ? routesForNotAuthenticatedOnly : []
) checks whether the user is authenticated (token exists). If not, it contains the routesForNotAuthenticatedOnly array; otherwise, it contains an empty array.
const router = createBrowserRouter([ ...routesForPublic, ...(!token ? routesForNotAuthenticatedOnly : []), ...routesForAuthenticatedOnly, ]);
-
Use RouterProvider to inject routing configuration
- The RouterProvider component wraps routing configuration and makes it available to the entire application
return <RouterProvider router={router} />;
Complete code
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { useAuth } from "../provider/authProvider"; import { ProtectedRoute } from "./ProtectedRoute"; const Routes = () => { const { token } = useAuth(); //Public routing configuration const routesForPublic = [ { path: "/service", element:Service Page, }, { path: "/about-us", element:About Us, }, ]; // Routing configuration that only authorized users can access const routesForAuthenticatedOnly = [ { path: "/", element:, // Wrap the component in ProtectedRoute children: [ { path: "/", element: User Home Page, }, { path: "/profile", element:User Profile, }, { path: "/logout", element:Logout, }, ], }, ]; // Routing configuration that can only be accessed by unauthorized users const routesForNotAuthenticatedOnly = [ { path: "/", element:Home Page, }, { path: "/login", element:Login, }, ]; // Merge routing configuration 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 App.jsx
-
Import necessary components and files
AuthProvider
is a component imported from the./provider/authProvider
file. It provides the authentication context for the entire application- Import
Routes
from./routes
. It defines application routing
import AuthProvider from "./provider/authProvider"; import Routes from "./routes";
-
Use the
AuthProvider
component to wrap theRoutes
component- The
AuthProvider
component is used to provide authentication context to the application. It wraps theRoutes
component, making the authentication context available to all components in theRoutes
component tree
- The
return ( <AuthProvider> <Routes /> </AuthProvider> );
Complete code
import AuthProvider from "./provider/authProvider"; import Routes from "./routes"; function App() { return ( <AuthProvider> <Routes /> </AuthProvider> ); } export default App;
Realize login and logout
Create login page in 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 useAuth hook to import the
setToken
function from the authentication context - Import the navigate function from
react-router-dom
to handle routing 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 homepage (“/”) 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, where it acts as a placeholder text
Now, we create a logout page in 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;
- In the logout page, we called the
setToken
function without passing parameters, which is equivalent to callingsetToken(null)
Now we will replace the login and logout components in the routing component with updated versions
const routesForNotAuthenticatedOnly = [ { path: "/", element: <div>Home Page</div>, }, { path: "/login", element: <Login />, }, ];
In the routesForNotAuthenticatedOnly
array, the element
attribute of “/login”
is set to
, which means that when the user When accessing 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
attribute of “/logout”
is set to
, which means that when the user When accessing the "/logout"
path, the Logout
component is rendered
Testing process
- When you first visit the root page
/
, you will see “Home page” in theroutesForNotAuthenticatedOnly
array - If you navigate to
/login
, after a 3 second delay, 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 functions in thereact-router-dom
library. After redirection, you will see “User Home Page” in theroutesForAuthenticatedOnly
array - If you then access
/logout
, after a 3 second delay, the logout process will be simulated. It will clear the authentication token by calling thesetToken
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 ” in theroutesForNotAuthenticatedOnly
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.
The above is the entire content of this article. Thank you everyone for your support of this article ~ Welcome to like and collect it, and leave your opinions in the comment area