In modern web applications, ensuring secure access to different features and resources is important. Authentication and authorization play a vital role in safeguarding user data and controlling access to various parts of an application.
Authentication verifies the identity of users, typically through credentials such as username and password. Authorization, on the other hand, controls what resources and actions users can access based on their authenticated identity and assigned roles. In React JS, we can implement these functionalities using various libraries and techniques.
To get started, create a new React JS project using your preferred setup (e.g., create-react-app or Next.js). Install the necessary dependencies, including a routing library (such as React Router) and an HTTP client (e.g., Axios) for API calls.
Create a project using the following command:
npx create-react-app your_project_name
To implement JWT (JSON Web Token) authentication in your React client, we need to make two API calls: one for user registration and another for user login. On form submission, make an API call to the server to validate the user’s credentials. If successful, store the authentication token received from the server securely (e.g., using the browser’s local storage or a state management library like Redux).
By following this flow and properly handling the JWT token in your React client, you can implement JWT authentication and protect your resources effectively.
Let's see the implementation of the login and registration form.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Your App</title> </head> <body> <div id="root"></div> <div id="dialog-root"></div> </body> </html>
In your routing configuration, specify which routes require authentication (private routes) and which are accessible to everyone (public routes). For private routes, redirect unauthenticated users to the login page.
// App.jsimport React from 'react';import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';import LoginForm from './LoginForm';import Dashboard from './Dashboard';const PrivateRoute = ({ component: Component, isAuthenticated, ...rest }) => (<Route{...rest}render={(props) =>isAuthenticated ? (<Component {...props} />) : (<Redirect to="/login" />)}/>);const PublicRoute = ({ component: Component, isAuthenticated, restricted, ...rest }) => (<Route{...rest}render={(props) =>isAuthenticated && restricted ? (<Redirect to="/dashboard" />) : (<Component {...props} />)}/>);const App = () => {const isAuthenticated = false; // Replace with actual authentication logicreturn (<Router><Switch><PublicRouterestricted={false}isAuthenticated={isAuthenticated}component={LoginForm}path="/login"exact/><PrivateRouteisAuthenticated={isAuthenticated}component={Dashboard}path="/dashboard"exact/></Switch></Router>);};export default App;
Lines 7–18: This component is used to protect private routes that should only be accessible by authenticated users. The component checks if the user is authenticated. If they are, it renders the provided Component
, passing any received props. If not, it redirects the user to the login page.
Lines 20–31: This component defines public routes that should be accessible to all users, including authenticated users. It checks if the user is authenticated and if the route is restricted. If both conditions are met, it redirects the user to the dashboard. Otherwise, it renders the provided Component
, passing any received props.
Lines 33–55: It sets the initial value of isAuthenticated
to false
. Within the Router
component, it renders a Switch
component that contains two routes: the PublicRoute
for the login page and the PrivateRoute
for the dashboard page. The exact
prop ensures that the routes match exactly.
To maintain user sessions, store the authentication token received from the server. Retrieve and validate the token during application startup or page reloads. If the token is valid, consider the user authenticated and allow access to private routes. If the token is expired or invalid, redirect the user to the login page.
// AuthContext.jsimport React, { createContext, useEffect, useState } from 'react';const AuthContext = createContext();const AuthProvider = ({ children }) => {const [isAuthenticated, setIsAuthenticated] = useState(false);useEffect(() => {const token = localStorage.getItem('token');// Validate token with the server and set isAuthenticated accordinglysetIsAuthenticated(!!token);}, []);return <AuthContext.Provider value={isAuthenticated}>{children}</AuthContext.Provider>;};export { AuthContext, AuthProvider };
Line 4: This line creates a new context object called AuthContext
using the createContext
function. Context provides a way to share data across components without explicitly passing it through props at every level.
Line 6: The component AuthProvider
which will be responsible for providing authentication-related data to its children components. It takes a children
prop, which represents the child components that this provider will wrap.
Line 7: This line uses the useState
hook to create a new state variable isAuthenticated
and a corresponding function setIsAuthenticated
to update its value.
Lines 9–13: This is an effect hook that runs after the initial rendering of the component. It retrieves a token from the browser's localStorage
using the key 'token'. The token is then used to validate the user's authentication status with the server. If a valid token is found, isAuthenticated
is set to true
using the setIsAuthenticated
function. Otherwise, it is set to false.
Line 15: This line returns a JSX element that wraps the children
components with the AuthContext.Provider
. It provides the isAuthenticated
value as the context value, making it accessible to any child components that access this context.
To protect sensitive API endpoints, include the authentication token in the request headers for authenticated requests. Create an
// axiosConfig.jsimport axios from 'axios';const instance = axios.create({baseURL: 'https://api.example.com',});instance.interceptors.request.use((config) => {const token = localStorage.getItem('token');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;});export default instance;
Lines 4–6: This line creates a new Axios instance using the axios.create
method. The instance is assigned to the instance
variable.
Lines 8: This code sets up a request interceptor for the Axios instance. The interceptors.request.use
method allows you to intercept outgoing requests before they are sent. In this case, the interceptor function receives the config
object as a parameter, which represents the request configuration.
Lines 9–13: Interceptor function first retrieves a token from the browser's localStorage
using the key 'token'. If a token is found, it adds an 'Authorization' header to the request's config
object. Finally, the interceptor function returns the modified config
object, allowing the request to proceed with the updated configuration.
// Dashboard.jsimport React, { useContext, useEffect, useState } from 'react';import axios from './axiosConfig';import { AuthContext } from './AuthContext';const Dashboard = () => {const [data, setData] = useState([]);const isAuthenticated = useContext(AuthContext);useEffect(() => {const fetchData = async() => {try {const response = await axios.get('/api/data');setData(response.data);} catch (error) {console.error(error);}};if (isAuthenticated) {fetchData();}}, [isAuthenticated]);return (<div>{data.map((item) => (<div key={item.id}>{item.name}</div>))}</div>);};export default Dashboard;
Implement a logout mechanism that removes the authentication token and clears any user-specific data stored locally. Upon logout, redirect the user to the login page or a public landing page.
// LogoutButton.jsimport React from 'react';import { useHistory } from 'react-router-dom';const LogoutButton = () => {const history = useHistory();const handleLogout = () => {localStorage.removeItem('token');// Clear any other user-specific datahistory.push('/login');};return (<button onClick={handleLogout}>Logout</button>);};export default LogoutButton;
In cases where a user tries to access a resource, they are not authorized to handle the authorization errors. Display meaningful error messages or redirect the user to an appropriate page indicating the lack of access permissions.
Implementing authentication and authorization in React JS applications is important for maintaining data security and controlling user access. By following the steps outlined in this Answer, you can build an authentication and authorization system.
Free Resources