JSON Web Tokens (JWT) have revolutionised modern web authentication and authorisation. This article provides an in-depth look at JWT, including its structure, implementation, benefits, and best practices.
What is JWT?
JWT 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.
Key Features
Compact: JWTs can be sent through URL, POST parameter, or HTTP header
Self-contained: Contains all necessary information about the user
Digitally signed: Information can be verified and trusted
Structure of a JWT
A JWT consists of three parts separated by dots (`.`):
Example - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlZhbnNoIEdveWFsIiwiaWF0IjoxNTE2MjM5MDIyfQ.GTU3sl-n9zc-tzQemgtO_34xFhUJ0g-Bfy0HGlLOssw
Here are three parts separated by two dots.
1. Header
2. Payload
3. Signature
Explained Everything Below:-
Header
{
"alg": "HS256",
"typ": "JWT"
}
The header typically consists of two parts:
- The signing algorithm (e.g., HS256, RS256)
- The type of token (JWT)
Payload
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"it": 1516239022,
"exp": 1516242622
}
The payload contains the claims (statements about the user) and data. Common claims include:
- `sub` (subject)
- `iat` (issued at)
- `exp` (expiration time)
- Custom claims
Signature
The signature is created by:
1. Taking the encoded header
2. Taking the encoded payload
3. Adding a secret
4. Applying the algorithm specified in the header
Implementation Example
Backend Implementation (Node.js)
You can access this code on Git Hub here.
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Secret key (store securely in production)
const SECRET_KEY = 'your-secret-key';
// Middleware to verify JWT
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
// Login route - generates JWT
app.post('/login', (req, res) => {
// Validate user credentials (simplified for example)
const username = req.body.username;
const user = {
id: 1,
username: username,
role: 'user'
};
// Generate JWT
const token = jwt.sign(user, SECRET_KEY, { expiresIn: '1h' });
res.json({ token });
});
// Protected route
app.get('/protected', authenticateToken, (req, res) => {
res.json({ data: 'Protected data', user: req.user });
});
Frontend Implementation (JavaScript)
// Making authenticated requests
const makeAuthenticatedRequest = async () => {
const token = localStorage.getItem('jwt_token');
try {
const response = await fetch('http://api.example.com/protected', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
};
Benefits of Using JWT
1. Stateless Authentication
- No need to store session information on the server
- Reduces database queries
- Easier to scale horizontally
2. Cross-Domain/CORS
- Can be used across different domains
- Perfect for microservices architectures
3. Performance
- Lightweight
- Fast processing
- Reduced database lookups
4. Flexibility
- Can include any custom claims
- Works with various programming languages
- Supports different signing algorithms
Security Best Practices
1. Token Storage
- Store tokens securely (HttpOnly cookies preferred)
- Never store sensitive data in payload
- Use secure browser storage mechanisms
2. Token Configuration
const token = jwt.sign(payload, SECRET_KEY, {
expiresIn: '1h', // Short expiration time
algorithm: 'HS256', // Secure algorithm
audience: 'your-app-name', // Specific audience
issuer: 'your-company' // Specific issuer
});
3. Additional Security Measures
- Implement a token refresh mechanism
- Use HTTPS only
- Validate all inputs
- Implement rate-limiting
Common Pitfalls and Solutions
1. Token Size
- Keep payload minimal
- Avoid storing unnecessary data
2. Token Revocation
Implement a token blacklist for critical situations:
const revokedTokens = new Set();
const checkIfTokenRevoked = (token) => {
return revokedTokens.has(token);
};
// In your middleware
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (checkIfTokenRevoked(token)) {
return res.sendStatus(403);
}
// Continue with verification...
};
USE CASE
1. Single Sign-On (SSO) [Share authentication across multiple applications,Maintain consistent user experience]
2. API Authentication [Secure API endpoints, Manage user permissions]
3. Mobile Applications [Stateless authentication, Efficient token management]
Comparison with Other Authentication Methods
JWT vs Session-Based Authentication
Output
Conclusion
JWT provides a scalable solution for creating a modern authentication system. It is far better than traditional or server-side session systems when implemented correctly with proper security measures. Its self-contained nature and flexibility make it particularly suitable for distributed systems and microservices architectures.
Get all the code on GitHub here.
When implementing JWT in your applications, remember to follow security best practices and the latest security recommendations.
Comments