Understanding JSON Web Tokens (JWT): A Complete Guide
JSON Web Tokens (JWT) have become the standard for authentication in modern web applications. This guide explains how JWTs work, their structure, best practices, and common security pitfalls.
What is a JWT?
A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe token format used to securely transmit information between parties as a JSON object. JWTs are commonly used for:
- Authentication: Verifying user identity after login
- Authorization: Controlling access to resources based on user roles
- Information Exchange: Securely transmitting data between services
JWT Structure
A JWT consists of three parts separated by dots (.):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each part is Base64URL-encoded:
1. Header
The header specifies the token type and the signing algorithm.
{
"alg": "HS256",
"typ": "JWT"
}
| Algorithm | Type | Description |
|---|---|---|
HS256 | Symmetric | HMAC with SHA-256 (shared secret) |
HS384 | Symmetric | HMAC with SHA-384 |
HS512 | Symmetric | HMAC with SHA-512 |
RS256 | Asymmetric | RSA with SHA-256 (public/private key) |
ES256 | Asymmetric | ECDSA with SHA-256 |
2. Payload (Claims)
The payload contains the claims — statements about the user and additional metadata.
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622,
"iss": "https://auth.example.com"
}
Registered Claims (Standard)
| Claim | Name | Description |
|---|---|---|
iss | Issuer | Who issued the token |
sub | Subject | Who the token is about (usually user ID) |
aud | Audience | Who the token is intended for |
exp | Expiration | When the token expires (Unix timestamp) |
nbf | Not Before | Token is not valid before this time |
iat | Issued At | When the token was created |
jti | JWT ID | Unique identifier for the token |
3. Signature
The signature ensures the token hasn't been tampered with. It's created by signing the encoded header and payload with a secret key.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Advertisement
JWT Authentication Flow
- Login: User sends credentials (email/password) to the server
- Token Generation: Server verifies credentials and creates a JWT
- Token Storage: Client stores the JWT (usually in memory or httpOnly cookie)
- Authenticated Requests: Client sends JWT in the
Authorizationheader - Token Verification: Server verifies the JWT signature and checks claims
Implementation Examples
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
// Generate token
function generateToken(user) {
return jwt.sign(
{
sub: user.id,
name: user.name,
role: user.role
},
SECRET,
{ expiresIn: '1h' }
);
}
// Verify token (middleware)
function 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, (err, decoded) => {
if (err) return res.sendStatus(403);
req.user = decoded;
next();
});
}
// Protected route
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ user: req.user });
});
Python (PyJWT)
import jwt
import datetime
SECRET = 'your-secret-key'
# Generate token
def generate_token(user_id, role):
payload = {
'sub': user_id,
'role': role,
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
return jwt.encode(payload, SECRET, algorithm='HS256')
# Verify token
def verify_token(token):
try:
payload = jwt.decode(token, SECRET, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None # Token expired
except jwt.InvalidTokenError:
return None # Invalid token
PHP
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secret = getenv('JWT_SECRET');
// Generate token
$payload = [
'sub' => $userId,
'name' => $userName,
'iat' => time(),
'exp' => time() + 3600 // 1 hour
];
$token = JWT::encode($payload, $secret, 'HS256');
// Verify token
try {
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
$userId = $decoded->sub;
} catch (Exception $e) {
// Token invalid or expired
http_response_code(401);
exit('Unauthorized');
}
Access Token + Refresh Token Pattern
For better security, use short-lived access tokens with long-lived refresh tokens:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
"expiresIn": 900,
"tokenType": "Bearer"
}
- Access Token: Short-lived (15 min), used for API requests
- Refresh Token: Long-lived (7 days), used only to get new access tokens
Security Best Practices
| Practice | Why |
|---|---|
| Use strong secrets (256+ bits) | Prevents brute-force attacks on HMAC signatures |
| Set short expiration times | Limits the window of compromise if a token is stolen |
| Never store JWTs in localStorage | Vulnerable to XSS attacks |
| Use httpOnly cookies for storage | Not accessible via JavaScript |
Always validate exp and iss | Prevents expired/forged tokens |
| Use asymmetric keys (RS256) for distributed systems | Services can verify without knowing the secret |
| Never put sensitive data in the payload | JWT payload is only Base64-encoded, not encrypted |
| Implement token revocation | Allows invalidating tokens before expiry |
Validate the alg header | Prevents "alg: none" attacks |
When NOT to Use JWTs
- Session management for simple apps: Server-side sessions with cookies are simpler and easier to revoke
- Storing large amounts of data: JWTs are sent with every request — keep them small
- When you need instant revocation: JWTs are valid until they expire unless you implement a blocklist
JWT vs. Session Cookies
| Feature | JWT | Session Cookie |
|---|---|---|
| Stateless | Yes | No (server stores session) |
| Scalability | Excellent (no shared state) | Requires session store |
| Revocation | Difficult (needs blocklist) | Easy (delete session) |
| Cross-domain | Easy (via header) | Difficult (cookie restrictions) |
| Mobile-friendly | Yes | Limited |
| Payload size | Larger (contains claims) | Small (just session ID) |
Conclusion
JWTs are a powerful tool for authentication and authorization in modern applications. They enable stateless, scalable architectures and work seamlessly across different platforms and services. However, they come with security responsibilities — always follow best practices to keep your users and data safe.