Jwt Misconfiguration with Bearer Tokens
How Jwt Misconfiguration Manifests in Bearer Tokens
Bearer tokens using JWTs are particularly vulnerable to misconfiguration because the entire authentication mechanism hinges on proper token handling. The most common manifestation occurs when developers fail to validate the token's signature, allowing attackers to forge tokens or modify existing ones without detection.
Consider this vulnerable implementation:
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token); // ❌ NO VERIFICATION
const userId = decoded.sub;
res.json({ data: await getUserData(userId) });This code extracts the token but never verifies the signature, making it trivial for attackers to create valid-looking tokens. Another critical misconfiguration is using weak signing algorithms like HS256 with hardcoded secrets, or worse, allowing the 'none' algorithm:
// Vulnerable: accepts any token with 'none' algorithm
const decoded = jwt.decode(token, { complete: true });
if (decoded.header.alg === 'none') {
// Process without verification
}Expired tokens that aren't properly checked represent another common issue. Developers sometimes rely on client-side expiration checks or implement weak timestamp validation:
// Vulnerable: weak expiration check
const now = Math.floor(Date.now() / 1000);
const decoded = jwt.verify(token, secret, { ignoreExpiration: true });
if (decoded.exp < now - 86400) { // accepts tokens up to 24h expired
throw new Error('Token too old');
}Missing or weak claims validation is also prevalent. The 'aud' (audience) and 'iss' (issuer) claims should be validated against expected values, but many implementations skip this:
// Vulnerable: missing audience validation
const decoded = jwt.verify(token, secret);
// No check that decoded.aud matches your API identifierClock skew misconfiguration can also create vulnerabilities. Setting excessive clock tolerance allows tokens to be valid outside their intended window:
// Vulnerable: excessive clock tolerance
const decoded = jwt.verify(token, secret, { clockTolerance: 300 }); // 5 minutes
// Attacker can use token 5 minutes before/after valid periodBearer Tokens-Specific Detection
Detecting JWT misconfigurations in Bearer token implementations requires examining both the token structure and the verification logic. Start by inspecting the Authorization header format:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva...middleBrick's black-box scanning automatically tests for common JWT vulnerabilities by submitting modified tokens and analyzing responses. The scanner attempts signature bypass by removing signatures, changing algorithms to 'none', and using weak keys.
Key detection patterns include:
- Missing signature verification: The API accepts tokens with modified payloads
- Algorithm confusion: The server accepts 'none' or accepts RSA tokens signed with HMAC
- Weak key detection: The scanner tests against common weak keys and patterns
- Claim bypass: The API processes tokens missing required claims
- Expiration bypass: Tokens beyond their expiration are still accepted
For manual verification, use tools like jwt.io to decode tokens and inspect their structure. Look for:
// Decode token to inspect claims
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header); // Check algorithm
console.log(decoded.payload); // Check claimsNetwork monitoring during authentication reveals whether the server properly validates tokens. Watch for 200 responses to modified tokens or tokens with invalid signatures.
middleBrick specifically tests for 12 JWT-related security checks including:
| Check Type | What It Tests | Potential Impact |
|---|---|---|
| Signature Bypass | Modified tokens without valid signatures | Token forgery |
| Algorithm Confusion | None algorithm, HS256/RSA confusion | Authentication bypass |
| Claim Validation | Missing or invalid audience/issuer claims | Token replay across services |
| Expiration Bypass | Expired tokens being accepted | Extended access window |
The scanner provides specific findings with severity levels and remediation guidance, mapping issues to OWASP API Security Top 10 categories like 'Broken Object Level Authorization' and 'Cryptographic Failures'.
Bearer Tokens-Specific Remediation
Proper JWT configuration in Bearer token implementations requires strict adherence to validation best practices. The foundation is always verifying the token signature with the correct key:
// Secure implementation
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // Specify allowed algorithms
issuer: 'your-issuer', // Validate issuer
audience: 'your-audience' // Validate audience
});
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}Key management is critical. Never hardcode secrets and use environment-specific keys:
// Use environment-specific secrets
const secrets = {
development: process.env.JWT_DEV_SECRET,
production: process.env.JWT_PROD_SECRET
};
const secret = secrets[process.env.NODE_ENV];
if (!secret) throw new Error('Missing JWT secret');For RS256 tokens, ensure proper key handling:
// RS256 with public key
const decoded = jwt.verify(token, fs.readFileSync('public.pem'), {
algorithms: ['RS256'],
issuer: 'https://your-issuer.com',
audience: 'https://your-api.com'
});Implement strict clock tolerance to prevent timing attacks:
// Minimal clock tolerance
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
clockTolerance: 30 // Only 30 seconds tolerance
});Validate all required claims explicitly:
function validateClaims(decoded) {
const requiredClaims = ['sub', 'iat', 'exp', 'aud', 'iss'];
for (const claim of requiredClaims) {
if (!decoded.hasOwnProperty(claim)) {
throw new Error(`Missing required claim: ${claim}`);
}
}
// Additional business logic claims
if (decoded.role !== 'user' && decoded.role !== 'admin') {
throw new Error('Invalid role claim');
}
}Use middleware for consistent token validation across your API:
const jwtMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Bearer token required' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE
});
// Attach user to request
req.user = decoded;
next();
} catch (err) {
console.error('JWT verification failed:', err.message);
return res.status(401).json({ error: 'Invalid token' });
}
};
// Apply middleware to protected routes
app.use('/api/protected/*', jwtMiddleware);For enhanced security, implement token revocation and rotation strategies. Use refresh tokens with short-lived access tokens:
// Short-lived access token
const accessToken = jwt.sign(
{ userId, role, type: 'access' },
process.env.JWT_SECRET,
{ expiresIn: '15m', issuer: 'your-issuer' }
);
// Long-lived refresh token (stored securely)
const refreshToken = jwt.sign(
{ userId, role, type: 'refresh' },
process.env.REFRESH_SECRET,
{ expiresIn: '7d', issuer: 'your-issuer' }
);Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |