Authentication is a key part of building web applications. It allows you to verify the identity of users, manage their sessions, and protect sensitive data. One of the most popular ways to implement authentication in modern web applications is by using JSON Web Tokens (JWT).
JWT is an open standard for securely transmitting information between parties as a JSON object. It’s widely used for authentication because of its compact size, flexibility, and ability to be passed through URL parameters, HTTP headers, or cookies.
In this guide, we’ll walk through how to implement JWT-based authentication in a React application.
1. How JWT Authentication Works
- User Login: The user provides credentials (like username and password). The server verifies these credentials, and if they are valid, the server generates a JWT.
- JWT Storage: The generated JWT is sent to the client (React app) and is stored (usually in local storage or cookies).
- API Requests: For every subsequent request, the React app sends the JWT in the HTTP request (usually in the
Authorization
header). - Token Verification: The server verifies the JWT to ensure it’s valid, not expired, and signed by the correct secret key before allowing access to protected resources.
2. Setting Up JWT Authentication in React
Install Axios (for HTTP Requests)
We’ll use Axios to send HTTP requests to the server.
npm install axios
3. Creating the React App with JWT Authentication
Here’s a step-by-step implementation of JWT-based authentication in React.
1. Login Form Component
Create a simple login form where users can enter their credentials to log in.
import React, { useState } from 'react';
import axios from 'axios';
function Login({ setAuth }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('https://your-api-url.com/login', { email, password });
// Save JWT to localStorage or cookies
localStorage.setItem('token', response.data.token);
setAuth(true); // Set authentication state to true
} catch (error) {
setError('Invalid credentials. Please try again.');
}
};
return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Login</button>
</form>
</div>
);
}
export default Login;
- Handle Form Submission: The login form captures user credentials and sends them to the server.
- Save JWT: Upon successful authentication, the JWT is stored in
localStorage
.
2. App Component (Managing Authentication State)
In the App.js
, we need to manage the authentication state. We’ll track whether the user is authenticated or not.
import React, { useState, useEffect } from 'react';
import Login from './Login';
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
// Check if there's a valid token in localStorage
const token = localStorage.getItem('token');
if (token) {
setIsAuthenticated(true);
}
}, []);
return (
<div>
{isAuthenticated ? (
<h1>Welcome, you are logged in!</h1>
) : (
<Login setAuth={setIsAuthenticated} />
)}
</div>
);
}
export default App;
- Use
useEffect
: On initial render, we check if there’s a token stored inlocalStorage
. If there is, we assume the user is authenticated. - Display Based on Auth: If the user is authenticated, we show a welcome message; otherwise, the login form is shown.
3. Making Authenticated API Requests
To make authenticated API requests, we’ll need to include the JWT in the Authorization
header.
import axios from 'axios';
const api = axios.create({
baseURL: 'https://your-api-url.com',
});
// Set up a request interceptor to include the token in the headers
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default api;
- Interceptors: We use Axios interceptors to attach the JWT token to every request that requires authentication. This ensures that the token is included in the request headers automatically.
4. Example: Protected Resource Request
To fetch data from a protected API endpoint, you can use the Axios instance we configured earlier.
import React, { useEffect, useState } from 'react';
import api from './api';
function ProtectedPage() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await api.get('/protected-route'); // Protected endpoint
setData(response.data);
} catch (error) {
console.log('Error fetching protected data:', error);
}
};
fetchData();
}, []);
if (!data) return <div>Loading...</div>;
return (
<div>
<h1>Protected Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default ProtectedPage;
- Authenticated Request: The
api
instance will automatically include the JWT token in the headers for the request to the protected route.
4. Handling Token Expiry
JWT tokens typically have an expiration time, and once expired, users need to re-authenticate. You can handle this in multiple ways:
- Token Expiry Handling on the Client: If you detect an expired token in a response, you can either redirect the user to the login page or refresh the token (if you have a refresh token mechanism).
- Logout: When a user logs out, you should remove the token from
localStorage
.
Example of handling token expiry:
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response.status === 401) {
// Token expired or invalid
localStorage.removeItem('token');
window.location.href = '/login'; // Redirect to login page
}
return Promise.reject(error);
}
);
5. Logout Feature
To implement logout functionality, you can create a simple button that clears the stored JWT token and redirects the user to the login page.
function Logout() {
const handleLogout = () => {
localStorage.removeItem('token');
window.location.href = '/login';
};
return <button onClick={handleLogout}>Logout</button>;
}
export default Logout;
6. Security Considerations
- Store JWT Securely: JWT tokens should be stored securely. While storing them in
localStorage
is common, it’s vulnerable to cross-site scripting (XSS) attacks. Consider storing tokens in HTTP-only cookies to prevent access from JavaScript. - Token Expiration: Always handle token expiration gracefully by prompting users to log in again or refresh the token.
- Secure Connections: Ensure that your React app communicates with the backend over HTTPS to prevent man-in-the-middle (MITM) attacks.