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
.
Import the necessary modules and dependency packages:
Importaxios
for sending API requests
Import fromreact
createContext
useContext
useEffect
useMemo
anduseState
import axios from "axios";
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
UsecreateContext()
to create a context for authentication
createContext()
The empty context created is used to share authenticated data and functions between components of the
const AuthContext = createContext();
Creating the AuthProvider Component
This component is used as the authentication context for the provider
It receives children as props, representing the child components that will have access to the authentication context.
const AuthProvider = ({ children }) => {
};
Define a state namedtoken
usinguseState
-
token
This represents an authentication token.
If the token data exists, we will get it vialocalStorage.getItem("token")
-
const [token, setToken_] = useState(localStorage.getItem("token"));
CreatesetToken
function to update token data for authentication- This function will be used to update the authentication token
It uses thesetToken_
function to update the token data and stores the updated data in the local environment atlocalStorage.setItem()
.
const setToken = (newToken) => {
setToken_(newToken);
};
UseuseEffect()
to set the axios default authentication request header and save the authentication token data locally
Every timetoken
is updated, the effect function executes the
Iftoken
exists, it will be set as the request header for axios and saved to the local localStorage
Iftoken
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]);
Creating Memorized Contexts with
useMemo
This context contains thetoken
andsetToken
functions
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]
);
Injecting authentication contexts into self-components
- Wrapping subcomponents using
AuthContext.Provider
Pass the contextValue as the value of the provider.
- Wrapping subcomponents using
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
Export the useAuth hook for external use in the authentication context.
useAuth is a customized hook that allows subcomponents to easily access authentication information
export const useAuth = () => {
return useContext(AuthContext);
};
- 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
First we have to import the necessary dependencies fromreact-router-dom
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
Define theProtectedRoute
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 />;
};
In theProtectedRoute
component, we use a custom hook (useAuth) provided by AuthContext to get the token information
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
)
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!
Importing necessary dependencies
RouterProvider
andcreateBrowserRouter
are used to configure and provide routing capabilities-
useAuth
Run the context in which we access the identity verification -
ProtectedRoute
Component wrapped around checksum routes
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
Defining Routing Components
- This function component acts as an entry point for configuring application routing
const Routes = () => {
const { token } = useAuth();
};
Using the useAuth hook to access the authentication token
Call the useAuth hook to get a token from the authentication context.
const { token } = useAuth();
Define routes for all users (public routes)
routesForPublic
The 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 that route.
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
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>,
},
],
},
];
Define routes that can only be accessed by unauthorized users
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>,
},
];
Combine and determine routes based on authentication state
The createBrowserRouter function is used to create a routing configuration, which takes an array of routes as an input.
The extension operator (…) is used to combine multiple routing arrays into a single array
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,
]);
Injecting Routing Configurations with RouterProvider
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
Importing the necessary components and files
AuthProvider
is a component imported from the./provider/authProvider
file. It provides the authentication context for the entire application
ImportRoutes
from./routes
. It defines the application routing
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";
Use theAuthProvider
component to wrap theRoutes
component
AuthProvider
component is used to provide an authentication context to the application. It wraps theRoutes
component so that the authentication context can be used for all components in theRoutes
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 thesetToken
function from the authentication context
Import the navigate function fromreact-router-dom
for handling route jumps
Inside the component, there is ahandleLogin
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 thehandleLogin
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 thesetToken
function without passing a parameter, which is equivalent to calling thesetToken(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
When you first visit the root page/
, you will see ” Home page ” in theroutesForNotAuthenticatedOnly
array.
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 thereact-router-dom
library. After the redirection, you will see “User Home Page” from theroutesForAuthenticatedOnly
array
If you then visit/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” from 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.