What are JSON Web Tokens?
JSON Web Tokens (JWTs) are compact, self-contained tokens that allow secure transmission of information between parties as a JSON object. This information can be verified and trusted because it is digitally signed using a secret (with HMAC algorithm) or a public/private key pair (using RSA or ECDSA).
Real-World Analogy
Think of a JWT as a tamper-proof ID card. When you present your ID at a secure building:
- The security desk (server) issues you an ID card (JWT)
- The ID contains your photo and credentials (payload)
- It has special holographic seals that are difficult to forge (signature)
- Anyone in the building can look at your ID to verify you're authorized to be there
- If someone tries to alter the ID, the holographic seals would show evidence of tampering
Why Use JWTs?
JWTs solve several challenges in modern web applications:
- Stateless authentication: Servers don't need to store session information
- Cross-domain communication: Tokens can be used across different domains
- Performance: Reduces database lookups for user information
- Mobile ready: Works well with native mobile apps
- Microservices: Enables secure communication between services
Practical Application
Some common real-world uses of JWTs include:
- Authentication: After login, each subsequent request includes the JWT
- Information Exchange: Securely transmitting information between parties
- Single Sign-On (SSO): One token works across multiple services (like Google accounts)
JWT Structure
A JWT consists of three parts separated by dots (.): Header, Payload, and Signature.
xxxxx.yyyyy.zzzzz
Header
The header typically consists of two parts: the type of token (JWT) and the signing algorithm being used (like HMAC SHA256 or RSA).
{
"alg": "HS256",
"typ": "JWT"
}
Payload
The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims.
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
Common Payload Claims
- iss (Issuer): Who issued the token
- sub (Subject): Who the token refers to
- aud (Audience): Who the token is intended for
- exp (Expiration Time): When the token expires
- nbf (Not Before): When the token becomes valid
- iat (Issued At): When the token was issued
- jti (JWT ID): Unique identifier for the token
Signature
The signature is created by taking the encoded header, the encoded payload, a secret, and signing them with the algorithm specified in the header.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
JWT Flow in Authentication
HTTP Header Example
JWTs are typically sent in the HTTP Authorization header using the Bearer schema:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Implementing JWTs in Node.js
Let's look at how to implement JWT authentication in a Node.js application using the jsonwebtoken package.
Installation
npm install jsonwebtoken
Creating a JWT Token
const jwt = require('jsonwebtoken');
// Secret key should be stored in environment variables
const SECRET_KEY = 'your-secret-key';
// Function to generate a token
function generateToken(user) {
const payload = {
sub: user.id,
name: user.name,
email: user.email,
role: user.role,
// Standard claims
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiration
};
return jwt.sign(payload, SECRET_KEY);
}
Verifying a JWT Token
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET_KEY);
return { valid: true, expired: false, decoded };
} catch (error) {
return {
valid: false,
expired: error.name === 'TokenExpiredError',
decoded: null
};
}
}
Middleware for Express.js
function authMiddleware(req, res, next) {
// Get token from Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' });
}
const token = authHeader.split(' ')[1];
const { valid, expired, decoded } = verifyToken(token);
if (!valid) {
return res.status(401).json({
message: expired ? 'Token has expired' : 'Invalid token'
});
}
// Attach user info to request
req.user = decoded;
next();
}
Complete Express.js Example
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const SECRET_KEY = 'your-secret-key';
const PORT = 3000;
// Mock user database
const users = [
{ id: 1, username: 'user1', password: 'password1', role: 'user' },
{ id: 2, username: 'admin', password: 'admin123', role: 'admin' }
];
// Login route
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Find user
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign(
{
sub: user.id,
username: user.username,
role: user.role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60)
},
SECRET_KEY
);
res.json({ token });
});
// Auth middleware
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
message: error.name === 'TokenExpiredError'
? 'Token has expired'
: 'Invalid token'
});
}
}
// Protected route
app.get('/protected', authenticate, (req, res) => {
res.json({
message: 'This is a protected route',
user: req.user
});
});
// Role-based access control
app.get('/admin', authenticate, (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ message: 'Access denied' });
}
res.json({ message: 'Admin panel' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
JWT Security Considerations
Best Practices
- Store the secret key securely: Use environment variables, not hard-coded values
- Keep tokens secure: Store them in HttpOnly cookies or secure storage
- Set proper expiration: Balance security and user experience
- Use HTTPS: Always transmit JWTs over encrypted connections
- Validate all tokens: Check signature and expiration every time
- Implement refresh tokens: For longer sessions with better security
Common Vulnerabilities
- Weak secret keys: Makes tokens easy to forge
- Excessive payload: Increases token size and can expose sensitive data
- Missing expiration: Creates tokens that never expire
- Client-side storage issues: localStorage is vulnerable to XSS attacks
- Token leakage: Through URLs, logs, or insecure transmission
Important Warning
Never store sensitive information like passwords or credit card details in a JWT payload. The payload is merely encoded (Base64Url), not encrypted, and can be easily decoded.
JWT vs. Session Authentication
| Feature | JWT Authentication | Session Authentication |
|---|---|---|
| Storage | Client-side | Server-side |
| Scalability | Excellent (stateless) | Requires session replication |
| Mobile/API | Well-suited | Additional work required |
| Cross-domain | Easy | Difficult (CORS issues) |
| Server load | Lower (no session lookups) | Higher (session verification) |
| Revocation | Difficult (requires blacklist) | Easy (delete session) |
Advantages & Disadvantages of JWTs
Advantages
- Stateless: No server-side storage needed
- Scalability: Works well with distributed systems and microservices
- Mobile-friendly: Great for native apps and SPAs
- Cross-domain: Facilitates cross-origin requests
- Performance: Reduces database lookups
- Decentralized: Multiple servers can accept the same token
Disadvantages
- Token size: Can become large with many claims
- Revocation challenges: Difficult to invalidate before expiration
- Secret management: Requires secure key handling
- Token theft risk: If stolen, can be used until expiration
- Implementation complexity: More complex than simple session cookies
Real-World JWT Applications
- Single Sign-On (SSO): One login for multiple applications
- API Authentication: Secure API access without sessions
- Mobile Applications: Authentication for native mobile apps
- Microservices: Secure communication between services
- Serverless Functions: Authentication for cloud functions
- Cross-domain Applications: Apps that span multiple domains
Real-World Example: Auth0
Auth0 is a popular authentication service that uses JWTs as part of its authentication flow. It manages token generation, validation, and refresh while providing additional security features like:
- Token rotation
- Blacklisting
- Anomaly detection
- Centralized token management
Practice Activities
Activity 1: Decode a JWT
Visit jwt.io and paste the following JWT to decode it:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.YDQ70Kbi-OGVhJH3P2dV2cZ6y2ioMYh4iP0kBRHy6YE
Questions:
- What algorithm is used for signing?
- When does the token expire?
- What role does the user have?
Activity 2: Create a Basic JWT Authentication System
Create a simple Express.js application that:
- Has a login route that generates JWT tokens
- Has a protected route that verifies tokens
- Implements role-based access control (admin vs. user)
Use the example code provided above as a starting point.
Activity 3: Implement Refresh Tokens
Extend the basic authentication system to implement a refresh token mechanism:
- Create short-lived access tokens (15 minutes)
- Create longer-lived refresh tokens (7 days)
- Add a route to refresh the access token using the refresh token
- Implement a token blacklist for revoked refresh tokens
Additional Resources
- JWT.io - Interactive debugging tool and library directory
- RFC 7519 - The JWT specification
- node-jsonwebtoken - The JWT library for Node.js
- JWT Best Current Practices - Security considerations
- JWT Best Practices with GraphQL - Specific to GraphQL APIs
Summary
- JSON Web Tokens are compact, self-contained tokens for secure information transmission
- They consist of three parts: header, payload, and signature
- JWTs enable stateless authentication, making them ideal for modern web applications
- They work well with mobile apps, SPAs, and microservices architectures
- Security considerations include proper storage, expiration, and transmission
- Common use cases include authentication, information exchange, and SSO
Next, we'll cover practical implementation strategies and token refresh mechanisms.