Introduction to JWT
JSON Web Tokens (JWTs) are an open standard (RFC 7519) for securely transmitting information between parties as a compact, self-contained JSON object. JWTs are commonly used for authentication and authorization in web applications, API security, single sign-on (SSO) implementations, and information exchange.
JWT Structure
A JWT consists of three parts separated by dots (.
):
- Header - Contains metadata about the token type and signing algorithm
- Payload - Contains the claims (statements about an entity)
- Signature - Verifies the token hasn’t been altered
Example JWT
1
| eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
Common JWT Security Vulnerabilities
1. Algorithm Vulnerabilities
None Algorithm Attack
1
2
3
4
5
6
7
8
9
10
11
| // Original header
{
"alg": "HS256",
"typ": "JWT"
}
// Modified malicious header
{
"alg": "none",
"typ": "JWT"
}
|
The none
algorithm indicates no signature verification is needed, potentially allowing attackers to forge tokens if accepted by the server.
Algorithm Confusion/Key Confusion Attack
1
2
3
4
5
6
7
8
9
10
11
| // Original header (symmetric algorithm)
{
"alg": "HS256",
"typ": "JWT"
}
// Modified to asymmetric algorithm
{
"alg": "RS256",
"typ": "JWT"
}
|
This attack exploits implementations that don’t validate the algorithm. An attacker might switch from RS256 (asymmetric) to HS256 (symmetric) and use the public key as the HMAC secret.
2. Weak Secret Keys
Using weak, short, or predictable secrets for HMAC-based tokens enables brute force attacks:
1
2
| # Example brute force attack using hashcat
hashcat -m 16500 -a 0 "JWT_TOKEN" wordlist.txt
|
3. Missing Signature Validation
Some implementations might:
- Skip signature verification entirely
- Check only that a signature exists without verifying it
- Accept tokens with truncated signatures
JWTs store information in base64url-encoded format, not encrypted by default:
1
2
3
| // Decoding a JWT payload (can be done by anyone)
const payload = atob(token.split('.')[1]);
console.log(JSON.parse(payload)); // All claims are visible
|
5. Token Replay Attacks
Without proper validation mechanisms, a token can be intercepted and reused:
1
2
| // Captured valid token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
6. Missing Expiration Time
Tokens without expiration remain valid indefinitely:
1
2
3
4
5
6
| // JWT payload without expiration
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
|
1
2
3
4
5
6
7
| // Maliciously constructed header
{
"alg": "HS256",
"typ": "JWT",
"cty": "nested-jwt",
"x5u": "http://attacker.com/key.pem"
}
|
Some implementations might fetch keys from the URL in x5u
.
Best Practices for JWT Security
1. Proper Algorithm Selection and Validation
1
2
| // Node.js example with explicit algorithm check
jwt.verify(token, secretKey, { algorithms: ['HS256'] });
|
2. Strong Secret Keys
1
2
| # Generate a strong random key (32 bytes = 256 bits)
openssl rand -hex 32
|
3. Implement Token Expiration
1
2
3
4
5
6
7
| // JWT payload with expiration
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622 // 1 hour after issued time
}
|
4. Use Additional Validation Claims
1
2
3
4
5
6
7
8
9
10
| // Enhanced JWT payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022, // Issued at timestamp
"exp": 1516242622, // Expiration time
"nbf": 1516239022, // Not valid before
"jti": "unique-token-id-123", // Unique identifier
"aud": "https://api.example.com" // Intended audience
}
|
5. Token Revocation Mechanisms
Options include:
- Token blacklisting in Redis/database
- Short expiration times with refresh tokens
- Version-based invalidation through user state
6. Secure Token Storage on Clients
1
2
| // Storing JWT in httpOnly cookie (browser-side)
document.cookie = "token=your_jwt_token; HttpOnly; Secure; SameSite=Strict";
|
7. Protect Against XSS and CSRF
- Use HttpOnly cookies to prevent JavaScript access
- Implement proper CSRF protection
- Validate token origin and audience
8. Consider Encrypted JWTs (JWE)
1
2
| # Example using jose CLI tool
jose encrypt --input="jwt.txt" --recipient=public_key.pem > encrypted_jwt.txt
|
JWT Testing Checklist
- Verify algorithm enforcement
- Test for “none” algorithm acceptance
- Check for algorithm confusion vulnerability
- Attempt token replay
- Verify expiration validation
- Check signature validation
- Test for header injection vulnerabilities
- Verify claim validation (issuer, audience)
- Test token revocation mechanism
- Check for brute force vulnerability with weak keys
Analysis and Debugging
- jwt.io: Online JWT decoder and debugger
- jwtXploiter: Tool for testing JWT vulnerabilities
- jwt_tool: Python tool for JWT testing
1
| python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
- Burp Suite JWT Extensions:
- JWT Scanner
- JSON Web Tokens
- JSON Web Token Attacker
Library-specific Security
Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| const jwt = require('jsonwebtoken');
// Secure token creation
const token = jwt.sign(
{ sub: userId, role: 'user' },
process.env.JWT_SECRET,
{
algorithm: 'HS256',
expiresIn: '1h',
notBefore: '0s',
audience: 'https://api.example.com',
issuer: 'https://auth.example.com'
}
);
// Secure verification
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
audience: 'https://api.example.com',
issuer: 'https://auth.example.com'
});
} catch (err) {
// Handle invalid tokens
}
|
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import jwt
from datetime import datetime, timedelta
# Secure token creation
payload = {
'sub': user_id,
'role': 'user',
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(hours=1),
'aud': 'https://api.example.com',
'iss': 'https://auth.example.com'
}
token = jwt.encode(payload, os.environ.get('JWT_SECRET'), algorithm='HS256')
# Secure verification
try:
decoded = jwt.decode(
token,
os.environ.get('JWT_SECRET'),
algorithms=['HS256'],
audience='https://api.example.com',
issuer='https://auth.example.com'
)
except jwt.InvalidTokenError:
# Handle invalid tokens
|
Real-world JWT Vulnerabilities
- CVE-2018-0114: Node.js
node-jose
library vulnerability allowed algorithm confusion attacks - CVE-2020-28042: Ruby JWT gem bypassing signature verification with trailing data
- CVE-2022-21449: Java’s ECDSA signature validation vulnerability allowing crafted JWTs with blank signatures
- CVE-2022-29156: Spring Security accepting JWTs with invalid signatures due to parser issues
Common JWT Attack Scenarios
1
2
3
| 1. Capture JWT from authorization header or cookies
2. Base64-decode the payload section
3. Extract sensitive information (emails, roles, permissions)
|
Scenario 2: Altering JWT Claims
1
2
3
4
5
| 1. Decode the existing JWT
2. Modify payload (e.g., change 'role': 'user' to 'role': 'admin')
3. Remove signature or attempt to bypass signature verification
4. Re-encode the tampered JWT
5. Use in authentication/authorization context
|
Scenario 3: Key Theft and Forgery
1
2
3
4
| 1. Identify JWT signing key location
2. Target vulnerabilities to extract the key (e.g., SSRF, path traversal)
3. Use the obtained key to forge valid tokens
4. Leverage forged tokens to access protected resources
|
Conclusion
JWT security requires a combination of:
- Understanding common vulnerabilities
- Implementing secure token handling practices
- Using secure configuration of JWT libraries
- Regular security testing of JWT implementations
- Maintaining awareness of new vulnerabilities
By following these best practices and security measures, organizations can leverage the benefits of JWTs while minimizing the associated security risks.