How to handle authentication and authorization in React JS
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 and authorization
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.
Setting up the project
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
Implementing user registration and login
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.
Implementation
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>
Securing routes with private and public access
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;
Explanation
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
isAuthenticatedtofalse. Within theRoutercomponent, it renders aSwitchcomponent that contains two routes: thePublicRoutefor the login page and thePrivateRoutefor the dashboard page. Theexactprop ensures that the routes match exactly.
Storing authentication tokens
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
AuthContextusing thecreateContextfunction. Context provides a way to share data across components without explicitly passing it through props at every level.Line 6: The component
AuthProviderwhich will be responsible for providing authentication-related data to its children components. It takes achildrenprop, which represents the child components that this provider will wrap.Line 7: This line uses the
useStatehook to create a new state variableisAuthenticatedand a corresponding functionsetIsAuthenticatedto 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
localStorageusing the key 'token'. The token is then used to validate the user's authentication status with the server. If a valid token is found,isAuthenticatedis set totrueusing thesetIsAuthenticatedfunction. Otherwise, it is set tofalse.Line 15: This line returns a JSX element that wraps the
childrencomponents with theAuthContext.Provider. It provides theisAuthenticatedvalue as the context value, making it accessible to any child components that access this context.
Protecting sensitive API endpoints
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;
Explanation
Lines 4–6: This line creates a new Axios instance using the
axios.createmethod. The instance is assigned to theinstancevariable.Lines 8: This code sets up a request interceptor for the Axios instance. The
interceptors.request.usemethod allows you to intercept outgoing requests before they are sent. In this case, the interceptor function receives theconfigobject as a parameter, which represents the request configuration.Lines 9–13: Interceptor function first retrieves a token from the browser's
localStorageusing the key 'token'. If a token is found, it adds an 'Authorization' header to the request'sconfigobject. Finally, the interceptor function returns the modifiedconfigobject, 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;
Logging out and clearing session
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;
Handling authorization errors
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.
Conclusion
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