Documentation Index
Fetch the complete documentation index at: https://guide.codepure.com/llms.txt
Use this file to discover all available pages before exploring further.
Common Misconfiguration
Weak or exposed JWT secrets allow attackers to forge tokens, bypass authentication, and gain unauthorized access to systems.
Vulnerable Example
// VULNERABLE - Hardcoded JWT secrets
const jwt = require('jsonwebtoken');
// Never hardcode JWT secrets!
const JWT_SECRET = 'my-super-secret-key'; // Weak secret
const REFRESH_SECRET = 'refresh-secret-123'; // Predictable
// Using HS256 with weak secret
function generateToken(userId) {
return jwt.sign(
{ userId, role: 'admin' },
JWT_SECRET,
{ expiresIn: '7d' } // Long expiration
);
}
// No token validation
function verifyToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (err) {
return null; // Silently fail
}
}
// Storing sensitive data in JWT
function createInsecureToken(user) {
return jwt.sign({
id: user.id,
email: user.email,
password: user.password, // Never include passwords!
creditCard: user.creditCard, // Never include sensitive data!
ssn: user.ssn
}, JWT_SECRET);
}
# VULNERABLE - Flask JWT implementation
from flask import Flask
from flask_jwt_extended import JWTManager, create_access_token
import datetime
app = Flask(__name__)
# Weak and hardcoded secrets
app.config['JWT_SECRET_KEY'] = 'super-secret' # Never hardcode!
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = datetime.timedelta(days=365) # Too long!
app.config['JWT_ALGORITHM'] = 'HS256' # Consider RS256 for better security
jwt = JWTManager(app)
# No refresh token rotation
@app.route('/login', methods=['POST'])
def login():
# ... authentication logic ...
access_token = create_access_token(
identity=user_id,
additional_claims={'role': 'admin', 'permissions': ['all']} # Too much info
)
return {'token': access_token}
# Using none algorithm (dangerous!)
def decode_token_unsafe(token):
import jwt
return jwt.decode(token, options={"verify_signature": False}) # Never do this!
Secure Example
// SECURE - Proper JWT implementation
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const fs = require('fs').promises;
class SecureJWTManager {
constructor() {
this.accessTokenSecret = null; // For HS256 in dev
this.refreshTokenSecret = null; // For HS256 in dev
this.publicKey = null;
this.privateKey = null;
this.isAsymmetric = false;
this.initialized = false;
}
async initialize() {
// Use RS256 with key pairs for production
if (process.env.NODE_ENV === 'production') {
await this.loadRSAKeys();
this.isAsymmetric = true;
} else {
// Use strong random secrets for development
this.loadHMACSecrets();
this.isAsymmetric = false;
}
this.initialized = true;
}
async loadRSAKeys() {
// Load RSA key pair from secure location
try {
this.privateKey = await fs.readFile(process.env.JWT_PRIVATE_KEY_PATH, 'utf8');
this.publicKey = await fs.readFile(process.env.JWT_PUBLIC_KEY_PATH, 'utf8');
} catch (error) {
console.error('Failed to load RSA keys:', error);
throw new Error('JWT key configuration failed');
}
}
loadHMACSecrets() {
// Load from environment with validation
const accessSecret = process.env.JWT_ACCESS_SECRET;
const refreshSecret = process.env.JWT_REFRESH_SECRET;
if (!accessSecret || accessSecret.length < 32) {
throw new Error('JWT_ACCESS_SECRET must be at least 32 characters');
}
if (!refreshSecret || refreshSecret.length < 32) {
throw new Error('JWT_REFRESH_SECRET must be at least 32 characters');
}
this.accessTokenSecret = accessSecret;
this.refreshTokenSecret = refreshSecret;
}
generateTokenPair(userId, userInfo = {}) {
// Minimal payload - don't include sensitive data
const iat = Math.floor(Date.now() / 1000);
const accessPayload = {
sub: userId,
type: 'access',
iat: iat
};
const algorithm = this.isAsymmetric ? 'RS256' : 'HS256';
// Access token - short lived
const accessToken = this.signToken(accessPayload, {
algorithm: algorithm,
expiresIn: '15m', // Short expiration
issuer: 'api.example.com',
audience: 'app.example.com'
});
// Refresh token - longer lived but still limited
const refreshPayload = {
sub: userId,
type: 'refresh',
iat: iat,
family: crypto.randomBytes(16).toString('hex') // Token family for rotation
};
const refreshToken = this.signToken(refreshPayload, {
algorithm: algorithm, // Use the same strong algorithm
expiresIn: '7d',
issuer: 'api.example.com'
});
return {
accessToken,
refreshToken,
expiresIn: 900 // 15 minutes in seconds
};
}
signToken(payload, options) {
let secret;
if (this.isAsymmetric) {
secret = this.privateKey;
} else {
secret = (payload.type === 'refresh' ? this.refreshTokenSecret : this.accessTokenSecret);
}
return jwt.sign(payload, secret, options);
}
verifyToken(token, isRefreshToken = false) {
try {
let secret, algorithms;
if (this.isAsymmetric) {
secret = this.publicKey;
algorithms = ['RS256'];
} else {
secret = isRefreshToken ? this.refreshTokenSecret : this.accessTokenSecret;
algorithms = ['HS256'];
}
const decoded = jwt.verify(token, secret, {
algorithms: algorithms,
issuer: 'api.example.com',
audience: isRefreshToken ? undefined : 'app.example.com', // Audience may not apply to refresh
clockTolerance: 30 // 30 seconds clock skew tolerance
});
// Additional validation
const expectedType = isRefreshToken ? 'refresh' : 'access';
if (decoded.type !== expectedType) {
throw new Error('Invalid token type');
}
return decoded;
} catch (error) {
// Log for security monitoring
console.error('Token verification failed:', error.message);
throw error;
}
}
async verifyRefreshTokenAndRotate(token) {
try {
const decoded = this.verifyToken(token, true);
// Check if token family is valid (for rotation detection)
// const isValidFamily = await this.checkTokenFamily(decoded.family);
// if (!isValidFamily) {
// // Possible token reuse - revoke all tokens for this user
// await this.revokeUserTokens(decoded.sub);
// throw new Error('Token reuse detected');
// }
// Invalidate old token
// await this.invalidateToken(token);
// Generate new token pair
return this.generateTokenPair(decoded.sub);
} catch (error) {
console.error('Refresh token verification failed:', error.message);
throw error;
}
}
// Generate cryptographically secure secret
static generateSecret(length = 64) {
return crypto.randomBytes(length).toString('base64');
}
}
// Express middleware for JWT validation
const jwtMiddleware = (jwtManager) => {
return async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwtManager.verifyToken(token, false);
req.user = decoded;
// Check if token is close to expiration
const now = Math.floor(Date.now() / 1000);
if (decoded.exp - now < 300) { // Less than 5 minutes
res.setHeader('X-Token-Expiring-Soon', 'true');
}
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
};
# SECURE - Kubernetes secret for JWT keys
apiVersion: v1
kind: Secret
metadata:
name: jwt-keys
namespace: production
type: Opaque
data:
jwt-private-key: LS0tLS1CRUdJTi... # Base64 encoded RSA private key
jwt-public-key: LS0tLS1CRUdJTi... # Base64 encoded RSA public key
# Fallback HMAC secrets (if needed, otherwise remove)
jwt-access-secret: <base64-encoded-random-secret>
jwt-refresh-secret: <base64-encoded-random-secret>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: jwt-config
data:
JWT_ALGORITHM: "RS256"
JWT_ISSUER: "api.example.com"
JWT_AUDIENCE: "app.example.com"
JWT_ACCESS_TOKEN_EXPIRES: "15m"
JWT_REFRESH_TOKEN_EXPIRES: "7d"
# Generate RSA keys for JWT
# Generate private key
openssl genpkey -algorithm RSA -out jwt-private.pem -pkeyopt rsa_keygen_bits:4096
# Generate public key
openssl rsa -pubout -in jwt-private.pem -out jwt-public.pem
# Generate random secrets for dev (HS256)
openssl rand -base64 64 > jwt-access-secret.txt
openssl rand -base64 64 > jwt-refresh-secret.txt
Detection Patterns
- JWT Token Format:
`eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+`
- Algorithm “none” Vulnerability:
`("alg"|"algorithm")\s*:\s*("none"|"None")`
- Common Weak Secret:
`(secret|password)['"]?\s*[:=]\s*['"](secret|12345|admin|jwt|super-secret)['"]`
- Hardcoded Secret Variable:
`(JWT_SECRET|SECRET_KEY)\s*[:=]\s*['"][^'"]{1,20}['"]` (Finds short, likely weak secrets)
Prevention Best Practices
- Use Strong, Random Secrets: A weak, guessable secret (like ‘secret123’) makes your token forgeable. Use a cryptographically secure random string (at least 256 bits / 32 characters) and load it from a secrets manager or environment variable.
- Prefer Asymmetric Algorithms (RS256):
HS256 (symmetric) uses one secret to sign and verify. RS256 (asymmetric) uses a private key to sign and a public key to verify. This is safer because you can share the public key with other services for verification without exposing the private signing key.
- Short Access Token Expiration: Access tokens (which grant access) should be very short-lived (e.g., 5-15 minutes). This dramatically limits the window of opportunity if a token is stolen.
- Implement Token Refresh: Use a separate, long-lived “refresh token” (e.g., 7 days) to get a new access token. This refresh token should be stored securely (as an
httpOnly cookie) and ideally implement rotation (where using a refresh token invalidates it and issues a new one).
- Minimal, Non-Sensitive Payload: A JWT is signed (tamper-proof) but not encrypted (it’s Base64 encoded, which is reversible). Anyone can read its contents. Never put sensitive data like passwords, permissions, or PII in the payload. Use the
sub (subject) claim to store the user ID and nothing more.
- Validate All Claims: On the server, always verify the signature. Also, verify the
exp (expiration), iss (issuer), and aud (audience) claims to ensure the token is not expired and was intended for your specific service.
- Implement Token Revocation: JWTs are stateless, which means they are valid until they expire. For critical events (like a user logging out or changing their password), you need a way to “revoke” their token. This usually involves maintaining a “denylist” (e.g., in Redis) of token IDs that are no longer valid.
- Secure Client-Side Storage: Do not store JWTs in
localStorage on the client, as it’s vulnerable to XSS attacks. Store them in secure, httpOnly cookies, which cannot be accessed by JavaScript.
- Monitor for Token Anomalies: Log and alert on token verification failures, attempts to use expired tokens, or impossible-to-achieve token refreshes. This can indicate an attacker is trying to forge or replay tokens.
- Rotate Your Keys: The secrets and private keys used to sign tokens should be rotated regularly (e.g., every 90 days). This limits the lifespan of any key that might have been leaked.