JWT Authentication: Complete Guide for Developers in 2025
JSON Web Tokens (JWT) have become the industry standard for authentication in modern web applications. This comprehensive guide covers everything from basic concepts to advanced security practices.
What is JWT?
JWT (JSON Web Token) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
JWT Structure
A JWT consists of three parts separated by dots (.):
xxxxx.yyyyy.zzzzz
Header.Payload.Signature1. Header
The header typically consists of two parts: the type of token (JWT) and the signing algorithm (HMAC, RSA, etc.).
{
"alg": "HS256",
"typ": "JWT"
}2. Payload
The payload contains the claims - statements about the user and additional data.
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"iat": 1516239022,
"exp": 1516242622
}3. Signature
The signature is created by encoding the header and payload, then signing them with a secret key.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)How JWT Authentication Works
Authentication Flow:
- 1. User logs in with credentials (username/password)
- 2. Server validates credentials and generates JWT
- 3. Server sends JWT back to client
- 4. Client stores JWT (localStorage, sessionStorage, or cookie)
- 5. Client includes JWT in Authorization header for subsequent requests
- 6. Server verifies JWT signature and extracts user information
- 7. Server processes request if JWT is valid
Implementing JWT Authentication
Backend Implementation (Node.js + Express)
Install required packages:
npm install jsonwebtoken bcrypt expressCreate authentication routes:
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const express = require('express');
const router = express.Router();
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = '7d';
// User registration
router.post('/register', async (req, res) => {
try {
const { email, password, name } = req.body;
// Validate input
if (!email || !password || !name) {
return res.status(400).json({
error: 'All fields are required'
});
}
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
error: 'User already exists'
});
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await User.create({
email,
password: hashedPassword,
name
});
// Generate JWT
const token = jwt.sign(
{
userId: user._id,
email: user.email
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
res.status(201).json({
message: 'User created successfully',
token,
user: {
id: user._id,
email: user.email,
name: user.name
}
});
} catch (error) {
res.status(500).json({
error: 'Registration failed'
});
}
});
// User login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// Validate input
if (!email || !password) {
return res.status(400).json({
error: 'Email and password are required'
});
}
// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
// Verify password
const validPassword = await bcrypt.compare(
password,
user.password
);
if (!validPassword) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
// Generate JWT
const token = jwt.sign(
{
userId: user._id,
email: user.email
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
res.json({
message: 'Login successful',
token,
user: {
id: user._id,
email: user.email,
name: user.name
}
});
} catch (error) {
res.status(500).json({
error: 'Login failed'
});
}
});
module.exports = router;Creating Authentication Middleware
// middleware/auth.js
const jwt = require('jsonwebtoken');
const authMiddleware = async (req, res, next) => {
try {
// Get token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'No token provided'
});
}
const token = authHeader.split(' ')[1];
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Add user info to request
req.user = {
userId: decoded.userId,
email: decoded.email
};
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired'
});
}
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
error: 'Invalid token'
});
}
res.status(500).json({
error: 'Authentication failed'
});
}
};
module.exports = authMiddleware;Using the Middleware
const authMiddleware = require('./middleware/auth');
// Protected route example
router.get('/profile', authMiddleware, async (req, res) => {
try {
const user = await User.findById(req.user.userId)
.select('-password');
res.json({ user });
} catch (error) {
res.status(500).json({ error: 'Failed to fetch profile' });
}
});
// Update profile
router.put('/profile', authMiddleware, async (req, res) => {
try {
const { name } = req.body;
const user = await User.findByIdAndUpdate(
req.user.userId,
{ name },
{ new: true }
).select('-password');
res.json({
message: 'Profile updated',
user
});
} catch (error) {
res.status(500).json({ error: 'Update failed' });
}
});Frontend Implementation (React)
Login Component
import { useState } from 'react';
import axios from 'axios';
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleLogin = async (e) => {
e.preventDefault();
setError('');
try {
const response = await axios.post('/api/auth/login', {
email,
password
});
// Store token
localStorage.setItem('token', response.data.token);
// Store user info
localStorage.setItem('user', JSON.stringify(response.data.user));
// Redirect to dashboard
window.location.href = '/dashboard';
} catch (err) {
setError(err.response?.data?.error || 'Login failed');
}
};
return (
<form onSubmit={handleLogin}>
{error && <div className="error">{error}</div>}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
);
}Axios Interceptor for Authentication
// utils/axios.js
import axios from 'axios';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL
});
// Request interceptor - add token to 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);
}
);
// Response interceptor - handle token expiration
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Token expired or invalid
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default api;Security Best Practices
1. Use Strong Secret Keys
// Generate a strong secret
const crypto = require('crypto');
const secret = crypto.randomBytes(64).toString('hex');
// Store in environment variables
// .env file
JWT_SECRET=your-super-secret-key-here-min-32-chars2. Set Appropriate Expiration Times
// Short-lived access tokens
const accessToken = jwt.sign(payload, JWT_SECRET, {
expiresIn: '15m'
});
// Long-lived refresh tokens
const refreshToken = jwt.sign(payload, REFRESH_SECRET, {
expiresIn: '7d'
});3. Implement Refresh Token Strategy
// Refresh token endpoint
router.post('/refresh', async (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh token required'
});
}
// Verify refresh token
const decoded = jwt.verify(
refreshToken,
process.env.REFRESH_SECRET
);
// Check if refresh token is blacklisted
const blacklisted = await RefreshToken.findOne({
token: refreshToken,
revoked: true
});
if (blacklisted) {
return res.status(401).json({
error: 'Token revoked'
});
}
// Generate new access token
const newAccessToken = jwt.sign(
{ userId: decoded.userId, email: decoded.email },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({
accessToken: newAccessToken
});
} catch (error) {
res.status(401).json({
error: 'Invalid refresh token'
});
}
});Common Security Vulnerabilities
1. Storing Sensitive Data in JWT
❌ Never store sensitive data in JWT:
- Passwords
- Credit card numbers
- Social security numbers
- Private keys
2. Not Validating Token Expiration
// Always verify expiration
try {
const decoded = jwt.verify(token, JWT_SECRET);
// Additional check (belt and suspenders)
if (decoded.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
} catch (error) {
// Handle expired or invalid token
}Conclusion
JWT authentication is a powerful tool for securing modern applications. By following these best practices and implementing proper security measures, you can build robust authentication systems that protect your users and data.
Try Our JWT Decoder Tool
Decode and inspect JWT tokens instantly with our free online tool!
Try JWT Decoder →