JWT Algorithm Confusion Attack (RS256 vs HS256)

Critical Risk Authentication & Authorization
jwtalgorithm-confusiontoken-forgerycryptographic-attackauthentication-bypassrs256hs256signature-validationasymmetric-crypto

What it is

A critical security vulnerability where applications incorrectly handle JWT (JSON Web Token) algorithm verification, allowing attackers to forge tokens by exploiting the confusion between asymmetric (RS256) and symmetric (HS256) algorithms. This occurs when an application accepts both algorithm types but validates them incorrectly, enabling attackers to sign tokens with the public key instead of the private key, completely bypassing authentication and authorization controls.

# VULNERABLE: JWT implementation susceptible to algorithm confusion import jwt import json import base64 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import serialization class VulnerableJWTService: def __init__(self): # Generate RSA key pair for demonstration self.private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048 ) self.public_key = self.private_key.public_key() # Serialize public key for algorithm confusion attack self.public_key_pem = self.public_key.public_key_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) self.private_key_pem = self.private_key.private_key_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) def create_rs256_token(self, payload): """Create a legitimate RS256 token""" return jwt.encode(payload, self.private_key_pem, algorithm='RS256') def verify_token_vulnerable(self, token): """VULNERABLE: Accepts algorithm from token header""" try { # CRITICAL VULNERABILITY: Using public key for verification # without specifying algorithm allows algorithm confusion decoded = jwt.decode(token, self.public_key_pem, algorithms=['RS256', 'HS256']) # Allows both! return {'valid': True, 'payload': decoded} except jwt.InvalidTokenError as e: return {'valid': False, 'error': str(e)} def verify_token_flexible(self, token): """VULNERABLE: Dynamic algorithm selection""" # Parse header to determine algorithm header = json.loads(base64.b64decode(token.split('.')[0] + '==')) algorithm = header.get('alg') try: if algorithm == 'RS256': # Use public key for RSA decoded = jwt.decode(token, self.public_key_pem, algorithms=['RS256']) elif algorithm == 'HS256': # VULNERABLE: Use public key as HMAC secret! decoded = jwt.decode(token, self.public_key_pem, algorithms=['HS256']) elif algorithm == 'none': # VULNERABLE: Accept unsigned tokens decoded = jwt.decode(token, options={"verify_signature": False}) else: return {'valid': False, 'error': 'Unsupported algorithm'} return {'valid': True, 'payload': decoded, 'algorithm': algorithm} except jwt.InvalidTokenError as e: return {'valid': False, 'error': str(e)} # ATTACK DEMONSTRATION: Algorithm confusion exploit def demonstrate_algorithm_confusion_attack(): # Initialize vulnerable service service = VulnerableJWTService() # Step 1: Create legitimate RS256 token legitimate_payload = { 'user': 'normal_user', 'role': 'user', 'exp': 1234567890 } legitimate_token = service.create_rs256_token(legitimate_payload) print(f"Legitimate RS256 token: {legitimate_token}") # Step 2: Create malicious payload malicious_payload = { 'user': 'attacker', 'role': 'admin', # Privilege escalation! 'exp': 9999999999 } # Step 3: Create forged HS256 token using RSA public key as HMAC secret # This is the core of the algorithm confusion attack forged_token = jwt.encode(malicious_payload, service.public_key_pem, algorithm='HS256') print(f"Forged HS256 token: {forged_token}") # Step 4: Verify tokens using vulnerable verification print("\n--- Verification Results ---") # Legitimate token verification result1 = service.verify_token_vulnerable(legitimate_token) print(f"Legitimate token verification: {result1}") # Forged token verification - THIS SHOULD FAIL BUT MIGHT SUCCEED! result2 = service.verify_token_vulnerable(forged_token) print(f"Forged token verification: {result2}") # Demonstrate flexible verification vulnerability result3 = service.verify_token_flexible(forged_token) print(f"Flexible verification of forged token: {result3}") # Step 5: Show how attacker gains admin access if result2['valid'] and result2['payload']['role'] == 'admin': print("\n🚨 ATTACK SUCCESSFUL! Attacker gained admin access!") print(f"Attacker payload: {result2['payload']}") else: print("\nāœ… Attack failed - system properly secured") # Additional attack vectors def demonstrate_none_algorithm_attack(): """Demonstrate 'none' algorithm attack""" # Create unsigned token with 'none' algorithm header = {"alg": "none", "typ": "JWT"} payload = {"user": "attacker", "role": "admin", "exp": 9999999999} # Manually construct token header_b64 = base64.b64encode(json.dumps(header).encode()).decode().rstrip('=') payload_b64 = base64.b64encode(json.dumps(payload).encode()).decode().rstrip('=') # 'none' algorithm has empty signature unsigned_token = f"{header_b64}.{payload_b64}." print(f"Unsigned 'none' token: {unsigned_token}") return unsigned_token def demonstrate_key_confusion_attack(): """Demonstrate using different keys for same algorithm""" service = VulnerableJWTService() # Attacker obtains public key (often exposed in JWKS endpoint) public_key_content = service.public_key_pem.decode() # Create malicious token using public key as HMAC secret malicious_payload = { 'user': 'attacker', 'role': 'admin', 'iss': 'trusted-issuer', 'exp': 9999999999 } # Sign with public key as HMAC secret attack_token = jwt.encode(malicious_payload, public_key_content, algorithm='HS256') print(f"Key confusion attack token: {attack_token}") # This token will be accepted by vulnerable verification that uses # the same public key for both RS256 and HS256 verification return attack_token # Flask web application demonstrating vulnerable endpoints from flask import Flask, request, jsonify app = Flask(__name__) vulnerable_service = VulnerableJWTService() @app.route('/login', methods=['POST']) def login(): """Login endpoint that creates RS256 tokens""" username = request.json.get('username') password = request.json.get('password') # Simplified authentication (insecure for demo) if username and password: payload = { 'user': username, 'role': 'admin' if username == 'admin' else 'user', 'exp': 1234567890 } token = vulnerable_service.create_rs256_token(payload) return jsonify({'token': token}) return jsonify({'error': 'Invalid credentials'}), 401 @app.route('/protected', methods=['GET']) def protected_endpoint(): """Protected endpoint using vulnerable token verification""" auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'error': 'No token provided'}), 401 token = auth_header[7:] # Remove 'Bearer ' prefix # VULNERABLE: Use flexible verification result = vulnerable_service.verify_token_flexible(token) if not result['valid']: return jsonify({'error': 'Invalid token'}), 401 user_data = result['payload'] return jsonify({ 'message': 'Access granted', 'user': user_data['user'], 'role': user_data['role'], 'algorithm_used': result.get('algorithm', 'unknown') }) @app.route('/admin', methods=['GET']) def admin_endpoint(): """Admin endpoint that can be exploited""" auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'error': 'No token provided'}), 401 token = auth_header[7:] result = vulnerable_service.verify_token_flexible(token) if not result['valid']: return jsonify({'error': 'Invalid token'}), 401 user_data = result['payload'] # Vulnerable role check - can be bypassed with forged tokens if user_data.get('role') != 'admin': return jsonify({'error': 'Admin access required'}), 403 return jsonify({ 'message': 'Admin access granted', 'sensitive_data': 'This should only be accessible to real admins', 'user': user_data['user'], 'token_algorithm': result.get('algorithm') }) if __name__ == '__main__': print("=== JWT Algorithm Confusion Vulnerability Demo ===") # Run attack demonstrations demonstrate_algorithm_confusion_attack() print("\n" + "="*50) none_token = demonstrate_none_algorithm_attack() print("\n" + "="*50) key_confusion_token = demonstrate_key_confusion_attack() print("\n=== Starting vulnerable web server ===") print("Try the following attacks:") print("1. Use forged HS256 tokens on /protected and /admin endpoints") print("2. Use 'none' algorithm tokens") print("3. Observe how algorithm confusion bypasses authentication") app.run(debug=True, port=5000)
# SECURE: JWT implementation with proper algorithm validation import jwt import json import base64 import hmac import hashlib from datetime import datetime, timedelta from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization from typing import Dict, Any, Optional import logging # Configure secure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class SecureJWTService: def __init__(self, algorithm: str = 'RS256'): """Initialize with strict algorithm enforcement""" self.algorithm = algorithm self.validate_algorithm_support() # Generate separate keys for different algorithms if algorithm == 'RS256': self.setup_rsa_keys() elif algorithm == 'HS256': self.setup_hmac_keys() else: raise ValueError(f"Unsupported algorithm: {algorithm}") # Security configuration self.token_expiry = timedelta(minutes=15) self.issuer = 'secure-jwt-service' self.audience = 'secure-app' # Track security events self.security_events = [] def validate_algorithm_support(self): """Validate that the algorithm is supported and secure""" supported_algorithms = ['RS256', 'ES256', 'HS256'] insecure_algorithms = ['none', 'HS1', 'RS1'] if self.algorithm not in supported_algorithms: raise ValueError(f"Algorithm {self.algorithm} is not supported") if self.algorithm in insecure_algorithms: raise ValueError(f"Algorithm {self.algorithm} is insecure") def setup_rsa_keys(self): """Set up RSA keys for RS256 algorithm""" # Generate RSA key pair self.private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048 ) self.public_key = self.private_key.public_key() # Serialize keys self.private_key_pem = self.private_key.private_key_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) self.public_key_pem = self.public_key.public_key_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) logger.info("RSA keys generated for RS256 algorithm") def setup_hmac_keys(self): """Set up HMAC secret for HS256 algorithm""" import secrets # Generate cryptographically secure random secret (256 bits) self.hmac_secret = secrets.token_bytes(32) logger.info("HMAC secret generated for HS256 algorithm") def create_token(self, payload: Dict[str, Any], custom_expiry: Optional[timedelta] = None) -> str: """Create a JWT token with strict algorithm enforcement""" if not isinstance(payload, dict): raise ValueError("Payload must be a dictionary") # Add security claims now = datetime.utcnow() expiry = custom_expiry or self.token_expiry secure_payload = { **payload, 'iss': self.issuer, 'aud': self.audience, 'iat': int(now.timestamp()), 'exp': int((now + expiry).timestamp()), 'jti': self.generate_jti() # Unique token ID } try: if self.algorithm == 'RS256': token = jwt.encode(secure_payload, self.private_key_pem, algorithm='RS256') elif self.algorithm == 'HS256': token = jwt.encode(secure_payload, self.hmac_secret, algorithm='HS256') else: raise ValueError(f"Token creation not supported for {self.algorithm}") self.log_security_event('TOKEN_CREATED', { 'algorithm': self.algorithm, 'user': payload.get('user', 'unknown'), 'expiry': expiry.total_seconds() }) return token except Exception as e: self.log_security_event('TOKEN_CREATION_FAILED', { 'error': str(e), 'algorithm': self.algorithm }) raise def verify_token(self, token: str, validate_audience: bool = True, validate_issuer: bool = True) -> Dict[str, Any]: """Verify JWT token with strict algorithm validation""" if not token or not isinstance(token, str): raise ValueError("Token must be a non-empty string") # CRITICAL: Validate algorithm before verification self.validate_token_algorithm(token) # Prepare verification options options = { 'verify_signature': True, 'verify_exp': True, 'verify_iat': True, 'verify_aud': validate_audience, 'verify_iss': validate_issuer } try: if self.algorithm == 'RS256': decoded = jwt.decode( token, self.public_key_pem, algorithms=['RS256'], # STRICT: Only allow RS256 audience=self.audience if validate_audience else None, issuer=self.issuer if validate_issuer else None, options=options ) elif self.algorithm == 'HS256': decoded = jwt.decode( token, self.hmac_secret, algorithms=['HS256'], # STRICT: Only allow HS256 audience=self.audience if validate_audience else None, issuer=self.issuer if validate_issuer else None, options=options ) else: raise ValueError(f"Verification not supported for {self.algorithm}") # Additional security validations self.validate_token_claims(decoded) self.log_security_event('TOKEN_VERIFIED', { 'algorithm': self.algorithm, 'user': decoded.get('user', 'unknown'), 'jti': decoded.get('jti') }) return decoded except jwt.ExpiredSignatureError: self.log_security_event('TOKEN_EXPIRED', { 'algorithm': self.algorithm }) raise ValueError("Token has expired") except jwt.InvalidAudienceError: self.log_security_event('INVALID_AUDIENCE', { 'algorithm': self.algorithm }) raise ValueError("Invalid token audience") except jwt.InvalidIssuerError: self.log_security_event('INVALID_ISSUER', { 'algorithm': self.algorithm }) raise ValueError("Invalid token issuer") except jwt.InvalidAlgorithmError: self.log_security_event('ALGORITHM_MISMATCH', { 'expected': self.algorithm, 'token_header': self.parse_token_header(token) }) raise ValueError("Algorithm mismatch detected") except Exception as e: self.log_security_event('TOKEN_VERIFICATION_FAILED', { 'error': str(e), 'algorithm': self.algorithm }) raise ValueError("Token verification failed") def validate_token_algorithm(self, token: str): """Validate that token algorithm matches expected algorithm""" try: header = self.parse_token_header(token) token_algorithm = header.get('alg') # CRITICAL: Strict algorithm validation if token_algorithm != self.algorithm: self.log_security_event('ALGORITHM_CONFUSION_ATTEMPT', { 'expected': self.algorithm, 'provided': token_algorithm, 'severity': 'CRITICAL' }) raise ValueError(f"Algorithm confusion detected. Expected {self.algorithm}, got {token_algorithm}") # Check for dangerous algorithms dangerous_algorithms = ['none', 'HS1', 'RS1'] if token_algorithm in dangerous_algorithms: self.log_security_event('DANGEROUS_ALGORITHM_DETECTED', { 'algorithm': token_algorithm, 'severity': 'CRITICAL' }) raise ValueError(f"Dangerous algorithm detected: {token_algorithm}") except json.JSONDecodeError: raise ValueError("Invalid token header format") def parse_token_header(self, token: str) -> Dict[str, Any]: """Parse JWT header safely""" try: header_b64 = token.split('.')[0] # Add padding if necessary header_b64 += '=' * (4 - len(header_b64) % 4) header_json = base64.b64decode(header_b64).decode('utf-8') return json.loads(header_json) except (IndexError, json.JSONDecodeError, ValueError) as e: raise ValueError(f"Invalid token format: {e}") def validate_token_claims(self, payload: Dict[str, Any]): """Perform additional validation on token claims""" required_claims = ['iss', 'aud', 'iat', 'exp', 'jti'] for claim in required_claims: if claim not in payload: raise ValueError(f"Missing required claim: {claim}") # Validate token age max_token_age = timedelta(hours=24).total_seconds() token_age = datetime.utcnow().timestamp() - payload['iat'] if token_age > max_token_age: self.log_security_event('TOKEN_TOO_OLD', { 'age_hours': token_age / 3600, 'jti': payload.get('jti') }) raise ValueError("Token is too old") # Validate role claims for security if payload.get('role') in ['admin', 'superadmin']: self.log_security_event('HIGH_PRIVILEGE_TOKEN_USED', { 'user': payload.get('user'), 'role': payload.get('role'), 'jti': payload.get('jti') }) def generate_jti(self) -> str: """Generate unique token identifier""" import secrets return secrets.token_urlsafe(16) def log_security_event(self, event_type: str, details: Dict[str, Any]): """Log security events for monitoring""" event = { 'timestamp': datetime.utcnow().isoformat(), 'event_type': event_type, 'details': details } self.security_events.append(event) # Log to system logger if event_type in ['ALGORITHM_CONFUSION_ATTEMPT', 'DANGEROUS_ALGORITHM_DETECTED']: logger.error(f"SECURITY ALERT: {event_type} - {details}") else: logger.info(f"Security event: {event_type} - {details}") def get_security_report(self) -> Dict[str, Any]: """Get security events report""" return { 'algorithm_enforced': self.algorithm, 'total_events': len(self.security_events), 'recent_events': self.security_events[-10:], # Last 10 events 'report_generated': datetime.utcnow().isoformat() } # SECURE: Flask application with proper JWT handling from flask import Flask, request, jsonify from functools import wraps app = Flask(__name__) # Initialize secure JWT service with RS256 secure_jwt_service = SecureJWTService(algorithm='RS256') def require_auth(f): """Secure authentication decorator""" @wraps(f) def decorated_function(*args, **kwargs): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'error': 'Authentication required'}), 401 token = auth_header[7:] # Remove 'Bearer ' prefix try: # Strict token verification payload = secure_jwt_service.verify_token(token) request.current_user = payload return f(*args, **kwargs) except ValueError as e: return jsonify({ 'error': 'Authentication failed', 'message': str(e) }), 401 return decorated_function def require_role(required_role: str): """Role-based authorization decorator""" def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not hasattr(request, 'current_user'): return jsonify({'error': 'Authentication required'}), 401 user_role = request.current_user.get('role') if user_role != required_role: secure_jwt_service.log_security_event('UNAUTHORIZED_ACCESS_ATTEMPT', { 'user': request.current_user.get('user'), 'required_role': required_role, 'user_role': user_role }) return jsonify({'error': 'Insufficient permissions'}), 403 return f(*args, **kwargs) return decorated_function return decorator @app.route('/login', methods=['POST']) def secure_login(): """Secure login endpoint""" data = request.get_json() username = data.get('username') password = data.get('password') # Implement proper authentication here # This is simplified for demonstration if username and password: # In production, verify credentials against secure storage role = 'admin' if username == 'admin' else 'user' payload = { 'user': username, 'role': role } try: token = secure_jwt_service.create_token(payload) return jsonify({ 'token': token, 'expires_in': '15m', 'token_type': 'Bearer', 'algorithm': secure_jwt_service.algorithm }) except Exception as e: return jsonify({'error': 'Token creation failed'}), 500 return jsonify({'error': 'Invalid credentials'}), 401 @app.route('/protected', methods=['GET']) @require_auth def secure_protected(): """Protected endpoint with secure authentication""" return jsonify({ 'message': 'Access granted with secure authentication', 'user': request.current_user['user'], 'role': request.current_user['role'], 'algorithm': secure_jwt_service.algorithm, 'token_id': request.current_user.get('jti') }) @app.route('/admin', methods=['GET']) @require_auth @require_role('admin') def secure_admin(): """Admin endpoint with secure role validation""" return jsonify({ 'message': 'Secure admin access granted', 'sensitive_data': 'This is properly protected admin data', 'user': request.current_user['user'], 'algorithm': secure_jwt_service.algorithm }) @app.route('/security/report', methods=['GET']) @require_auth @require_role('admin') def security_report(): """Security monitoring endpoint""" return jsonify(secure_jwt_service.get_security_report()) @app.errorhandler(Exception) def handle_error(error): """Global error handler""" secure_jwt_service.log_security_event('APPLICATION_ERROR', { 'error': str(error), 'endpoint': request.endpoint }) return jsonify({ 'error': 'Internal server error' }), 500 if __name__ == '__main__': print("=== Secure JWT Service Started ===") print(f"Algorithm enforced: {secure_jwt_service.algorithm}") print("Algorithm confusion attacks are prevented") print("Security monitoring is active") app.run(debug=False, port=5000) # Debug disabled for security

šŸ’” Why This Fix Works

The vulnerable version demonstrates a complete JWT algorithm confusion attack where the application accepts multiple algorithms and incorrectly uses the RSA public key for both RS256 and HS256 verification. Attackers can forge HS256 tokens using the public key as an HMAC secret. The secure version implements strict algorithm enforcement, separate key management for different algorithms, comprehensive security monitoring, and proper validation to prevent all forms of algorithm confusion attacks.

Why it happens

Applications often use JWT libraries that accept the 'alg' parameter from the token header without proper validation, allowing attackers to specify which algorithm should be used for verification. This enables algorithm confusion attacks where RS256 tokens can be verified as HS256 tokens using the public key as the HMAC secret.

Root causes

Improper Algorithm Validation in JWT Libraries

Applications often use JWT libraries that accept the 'alg' parameter from the token header without proper validation, allowing attackers to specify which algorithm should be used for verification. This enables algorithm confusion attacks where RS256 tokens can be verified as HS256 tokens using the public key as the HMAC secret.

Preview example – JAVASCRIPT
// VULNERABLE: JWT verification without algorithm specification
const jwt = require('jsonwebtoken');
const fs = require('fs');

// Load RSA keys for RS256
const privateKey = fs.readFileSync('private-key.pem');
const publicKey = fs.readFileSync('public-key.pem');

class VulnerableJWTManager {
    // VULNERABLE: Token creation with RS256
    createToken(payload) {
        return jwt.sign(payload, privateKey, {
            algorithm: 'RS256',
            expiresIn: '1h',
            issuer: 'myapp'
        });
    }
    
    // VULNERABLE: No algorithm specification in verification
    verifyToken(token) {
        try {
            // CRITICAL VULNERABILITY: Uses publicKey for verification
            // but doesn't specify algorithm, allowing algorithm confusion
            const decoded = jwt.verify(token, publicKey);
            return { valid: true, payload: decoded };
        } catch (error) {
            return { valid: false, error: error.message };
        }
    }
    
    // VULNERABLE: Alternative verification method that accepts any algorithm
    verifyTokenFlexible(token) {
        try {
            // Parse header to get algorithm
            const header = JSON.parse(Buffer.from(token.split('.')[0], 'base64').toString());
            
            let key;
            if (header.alg === 'RS256') {
                key = publicKey;
            } else if (header.alg === 'HS256') {
                // VULNERABLE: Using public key as HMAC secret!
                key = publicKey;
            } else {
                throw new Error('Unsupported algorithm');
            }
            
            const decoded = jwt.verify(token, key, { algorithm: header.alg });
            return { valid: true, payload: decoded };
        } catch (error) {
            return { valid: false, error: error.message };
        }
    }
    
    // VULNERABLE: Multiple algorithm support without proper key management
    verifyMultiAlgorithm(token) {
        const algorithms = ['RS256', 'HS256', 'ES256'];
        
        for (const alg of algorithms) {
            try {
                // VULNERABLE: Same key used for different algorithms
                const decoded = jwt.verify(token, publicKey, { algorithm: alg });
                return { valid: true, payload: decoded, algorithm: alg };
            } catch (error) {
                continue; // Try next algorithm
            }
        }
        
        return { valid: false, error: 'No valid algorithm found' };
    }
}

// VULNERABLE: Express middleware using vulnerable JWT manager
const jwtManager = new VulnerableJWTManager();

function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    
    if (!token) {
        return res.sendStatus(401);
    }
    
    // VULNERABLE: Uses vulnerable verification
    const result = jwtManager.verifyToken(token);
    
    if (!result.valid) {
        return res.sendStatus(403);
    }
    
    req.user = result.payload;
    next();
}

// Example of exploitable endpoint
app.get('/admin', authenticateToken, (req, res) => {
    // This endpoint can be accessed with forged tokens
    if (req.user.role !== 'admin') {
        return res.sendStatus(403);
    }
    
    res.json({ message: 'Admin access granted', user: req.user });
});

Algorithm Confusion in Token Verification Logic

Many applications implement custom JWT verification logic that doesn't properly handle algorithm switching. When the same key material is used for both symmetric and asymmetric algorithms, attackers can exploit this by changing the algorithm in the JWT header and using the public key as an HMAC secret.

Preview example – JAVASCRIPT
// VULNERABLE: Custom JWT implementation with algorithm confusion
const crypto = require('crypto');
const base64url = require('base64url');

class CustomJWTProcessor {
    constructor(rsaPublicKey, hmacSecret) {
        this.rsaPublicKey = rsaPublicKey;
        this.hmacSecret = hmacSecret;
    }
    
    // VULNERABLE: Custom token verification with algorithm confusion
    verifyCustomToken(token) {
        const parts = token.split('.');
        if (parts.length !== 3) {
            throw new Error('Invalid token format');
        }
        
        const [headerB64, payloadB64, signatureB64] = parts;
        
        // Decode header and payload
        const header = JSON.parse(base64url.decode(headerB64));
        const payload = JSON.parse(base64url.decode(payloadB64));
        
        // VULNERABLE: Trust the algorithm from the token header
        const algorithm = header.alg;
        
        // Recreate the signing input
        const signingInput = `${headerB64}.${payloadB64}`;
        const signature = base64url.toBuffer(signatureB64);
        
        let isValid = false;
        
        if (algorithm === 'RS256') {
            // RSA verification
            const verifier = crypto.createVerify('RSA-SHA256');
            verifier.update(signingInput);
            isValid = verifier.verify(this.rsaPublicKey, signature);
        } else if (algorithm === 'HS256') {
            // VULNERABLE: Using RSA public key as HMAC secret!
            const expectedSignature = crypto
                .createHmac('sha256', this.rsaPublicKey)
                .update(signingInput)
                .digest();
            
            isValid = crypto.timingSafeEqual(signature, expectedSignature);
        } else if (algorithm === 'none') {
            // VULNERABLE: Accepting 'none' algorithm
            isValid = signature.length === 0;
        } else {
            throw new Error(`Unsupported algorithm: ${algorithm}`);
        }
        
        if (!isValid) {
            throw new Error('Invalid signature');
        }
        
        // Check expiration
        if (payload.exp && Date.now() >= payload.exp * 1000) {
            throw new Error('Token expired');
        }
        
        return payload;
    }
    
    // VULNERABLE: Key confusion in multi-tenancy setup
    verifyTenantToken(token, tenantId) {
        const parts = token.split('.');
        const header = JSON.parse(base64url.decode(parts[0]));
        const payload = JSON.parse(base64url.decode(parts[1]));
        
        // VULNERABLE: Using same key for different algorithms per tenant
        const tenantKey = this.getTenantKey(tenantId);
        
        if (header.alg === 'RS256') {
            // Use tenant key as RSA public key
            return this.verifyRS256(token, tenantKey);
        } else if (header.alg === 'HS256') {
            // VULNERABLE: Use same tenant key as HMAC secret
            return this.verifyHS256(token, tenantKey);
        }
        
        throw new Error('Unsupported algorithm for tenant');
    }
    
    getTenantKey(tenantId) {
        // VULNERABLE: Returns same key material for different algorithms
        const tenantKeys = {
            'tenant1': this.rsaPublicKey,
            'tenant2': this.rsaPublicKey,
            'default': this.rsaPublicKey
        };
        
        return tenantKeys[tenantId] || tenantKeys['default'];
    }
    
    verifyRS256(token, publicKey) {
        // Implementation for RS256 verification
        const parts = token.split('.');
        const signingInput = `${parts[0]}.${parts[1]}`;
        const signature = base64url.toBuffer(parts[2]);
        
        const verifier = crypto.createVerify('RSA-SHA256');
        verifier.update(signingInput);
        return verifier.verify(publicKey, signature);
    }
    
    verifyHS256(token, secret) {
        // VULNERABLE: Using RSA public key as HMAC secret
        const parts = token.split('.');
        const signingInput = `${parts[0]}.${parts[1]}`;
        const signature = base64url.toBuffer(parts[2]);
        
        const expectedSignature = crypto
            .createHmac('sha256', secret)
            .update(signingInput)
            .digest();
        
        return crypto.timingSafeEqual(signature, expectedSignature);
    }
}

// VULNERABLE: API Gateway with algorithm confusion
class VulnerableAPIGateway {
    constructor() {
        this.jwtProcessor = new CustomJWTProcessor(
            fs.readFileSync('public-key.pem'),
            process.env.HMAC_SECRET || 'default-secret'
        );
    }
    
    // VULNERABLE: Route handler that accepts confused tokens
    async authenticateRequest(req, res, next) {
        const token = req.headers.authorization?.replace('Bearer ', '');
        
        if (!token) {
            return res.status(401).json({ error: 'No token provided' });
        }
        
        try {
            // VULNERABLE: Accepts any algorithm specified in token
            const payload = this.jwtProcessor.verifyCustomToken(token);
            
            req.user = payload;
            req.user.authMethod = 'jwt';
            
            next();
        } catch (error) {
            return res.status(401).json({ error: 'Invalid token' });
        }
    }
    
    // VULNERABLE: Admin endpoint accessible via algorithm confusion
    setupRoutes(app) {
        app.use('/api', this.authenticateRequest.bind(this));
        
        app.get('/api/admin/users', (req, res) => {
            // This endpoint can be accessed with forged tokens
            if (req.user.role !== 'admin') {
                return res.status(403).json({ error: 'Insufficient privileges' });
            }
            
            res.json({
                users: ['user1', 'user2', 'admin'],
                requestedBy: req.user.username
            });
        });
        
        app.get('/api/sensitive-data', (req, res) => {
            // Another vulnerable endpoint
            res.json({
                sensitiveData: 'This should be protected',
                user: req.user
            });
        });
    }
}

Weak Configuration in JWT Libraries and Frameworks

Many JWT libraries have default configurations that are vulnerable to algorithm confusion attacks. This includes accepting multiple algorithms by default, using the same key for different algorithms, or not enforcing strict algorithm validation. Developers often use these libraries without understanding the security implications.

Preview example – JAVASCRIPT
// VULNERABLE: Various JWT library misconfigurations
const jwt = require('jsonwebtoken');
const express = require('express');
const fs = require('fs');

// Load keys
const publicKey = fs.readFileSync('public-key.pem', 'utf8');
const privateKey = fs.readFileSync('private-key.pem', 'utf8');
const hmacSecret = process.env.HMAC_SECRET || 'weak-secret';

class ConfigurationVulnerabilities {
    // VULNERABLE: Default library configuration
    verifyWithDefaults(token) {
        try {
            // VULNERABLE: No algorithm specified, accepts any
            const decoded = jwt.verify(token, publicKey);
            return decoded;
        } catch (error) {
            throw new Error('Token verification failed');
        }
    }
    
    // VULNERABLE: Accepting multiple algorithms with same key
    verifyMultipleAlgorithms(token) {
        const supportedAlgorithms = ['RS256', 'HS256', 'ES256'];
        
        for (const algorithm of supportedAlgorithms) {
            try {
                // VULNERABLE: Using public key for all algorithms
                const decoded = jwt.verify(token, publicKey, {
                    algorithms: [algorithm]
                });
                return { payload: decoded, algorithm };
            } catch (error) {
                continue; // Try next algorithm
            }
        }
        
        throw new Error('No supported algorithm worked');
    }
    
    // VULNERABLE: Dynamic algorithm selection
    verifyDynamicAlgorithm(token, userPreference) {
        try {
            // VULNERABLE: User can choose algorithm
            const algorithm = userPreference || 'RS256';
            
            let key;
            if (algorithm === 'RS256') {
                key = publicKey;
            } else if (algorithm === 'HS256') {
                // VULNERABLE: Using public key as HMAC secret
                key = publicKey;
            } else {
                key = hmacSecret;
            }
            
            const decoded = jwt.verify(token, key, {
                algorithms: [algorithm]
            });
            
            return decoded;
        } catch (error) {
            throw new Error('Dynamic verification failed');
        }
    }
    
    // VULNERABLE: Fallback verification with different algorithms
    verifyWithFallback(token) {
        // Try RS256 first
        try {
            return jwt.verify(token, publicKey, {
                algorithms: ['RS256']
            });
        } catch (rs256Error) {
            // VULNERABLE: Fallback to HS256 with public key
            try {
                return jwt.verify(token, publicKey, {
                    algorithms: ['HS256']
                });
            } catch (hs256Error) {
                // Final fallback with HMAC secret
                return jwt.verify(token, hmacSecret, {
                    algorithms: ['HS256']
                });
            }
        }
    }
    
    // VULNERABLE: Environment-based algorithm selection
    verifyEnvironmentAlgorithm(token) {
        const algorithm = process.env.JWT_ALGORITHM || 'RS256';
        
        let key;
        switch (algorithm) {
            case 'RS256':
                key = publicKey;
                break;
            case 'HS256':
                // VULNERABLE: Might use public key if HMAC_SECRET not set
                key = process.env.HMAC_SECRET || publicKey;
                break;
            default:
                throw new Error('Unsupported algorithm');
        }
        
        return jwt.verify(token, key, { algorithms: [algorithm] });
    }
}

// VULNERABLE: Express.js middleware with configuration issues
function createVulnerableAuthMiddleware() {
    const vulnerableAuth = new ConfigurationVulnerabilities();
    
    return (req, res, next) => {
        const token = req.headers.authorization?.replace('Bearer ', '');
        
        if (!token) {
            return res.status(401).json({ error: 'No token' });
        }
        
        try {
            // VULNERABLE: Using default verification
            req.user = vulnerableAuth.verifyWithDefaults(token);
            next();
        } catch (error) {
            return res.status(401).json({ error: 'Invalid token' });
        }
    };
}

// VULNERABLE: API routes using vulnerable middleware
const app = express();
app.use(express.json());

// Apply vulnerable authentication
app.use('/api', createVulnerableAuthMiddleware());

// VULNERABLE: Protected routes that can be bypassed
app.get('/api/profile', (req, res) => {
    res.json({
        user: req.user,
        message: 'This endpoint can be accessed with forged tokens'
    });
});

app.get('/api/admin/settings', (req, res) => {
    if (req.user.role !== 'admin') {
        return res.status(403).json({ error: 'Admin required' });
    }
    
    res.json({
        settings: {
            jwtSecret: hmacSecret, // Exposing secrets!
            algorithm: 'dynamic',
            securityLevel: 'low'
        }
    });
});

// VULNERABLE: Token creation endpoint with weak configuration
app.post('/auth/login', (req, res) => {
    const { username, password } = req.body;
    
    // Simulate authentication (vulnerable in real implementation)
    if (username && password) {
        const payload = {
            username,
            role: username === 'admin' ? 'admin' : 'user',
            iat: Math.floor(Date.now() / 1000),
            exp: Math.floor(Date.now() / 1000) + 3600
        };
        
        // VULNERABLE: Multiple token formats supported
        const rsToken = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
        const hsToken = jwt.sign(payload, hmacSecret, { algorithm: 'HS256' });
        
        res.json({
            rsToken,
            hsToken,
            message: 'Use either token for authentication'
        });
    } else {
        res.status(401).json({ error: 'Invalid credentials' });
    }
});

// VULNERABLE: Debug endpoint exposing key material
app.get('/debug/keys', (req, res) => {
    res.json({
        publicKey: publicKey,
        hmacSecret: hmacSecret,
        algorithms: ['RS256', 'HS256'],
        warning: 'This endpoint should not exist in production'
    });
});

Fixes

1

Implement Strict Algorithm Validation and Enforcement

Always specify the exact algorithm expected for JWT verification and never trust the algorithm specified in the token header. Use separate keys for different algorithms and implement strict validation to prevent algorithm confusion attacks.

View implementation – JAVASCRIPT
// SECURE: Strict algorithm validation and key management
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const fs = require('fs');

class SecureJWTManager {
    constructor() {
        // Load keys with proper validation
        this.rsaPrivateKey = this.loadKey('private-key.pem');
        this.rsaPublicKey = this.loadKey('public-key.pem');
        this.hmacSecret = this.loadHMACSecret();
        
        // Enforce single algorithm policy
        this.allowedAlgorithm = process.env.JWT_ALGORITHM || 'RS256';
        this.validateAlgorithmSupport();
    }
    
    loadKey(filename) {
        try {
            const key = fs.readFileSync(filename, 'utf8');
            if (!key || key.trim().length === 0) {
                throw new Error(`Invalid key file: ${filename}`);
            }
            return key;
        } catch (error) {
            throw new Error(`Failed to load key file ${filename}: ${error.message}`);
        }
    }
    
    loadHMACSecret() {
        const secret = process.env.JWT_HMAC_SECRET;
        if (!secret || secret.length < 32) {
            throw new Error('JWT_HMAC_SECRET must be at least 32 characters long');
        }
        return secret;
    }
    
    validateAlgorithmSupport() {
        const supportedAlgorithms = ['RS256', 'ES256', 'HS256'];
        if (!supportedAlgorithms.includes(this.allowedAlgorithm)) {
            throw new Error(`Unsupported algorithm: ${this.allowedAlgorithm}`);
        }
    }
    
    createToken(payload, options = {}) {
        // Validate payload
        if (!payload || typeof payload !== 'object') {
            throw new Error('Payload must be a valid object');
        }
        
        // Add security claims
        const securePayload = {
            ...payload,
            iat: Math.floor(Date.now() / 1000),
            iss: process.env.JWT_ISSUER || 'secure-app',
            aud: process.env.JWT_AUDIENCE || 'secure-app-users'
        };
        
        const tokenOptions = {
            algorithm: this.allowedAlgorithm,
            expiresIn: options.expiresIn || '15m',
            ...options
        };
        
        try {
            if (this.allowedAlgorithm === 'RS256') {
                return jwt.sign(securePayload, this.rsaPrivateKey, tokenOptions);
            } else if (this.allowedAlgorithm === 'HS256') {
                return jwt.sign(securePayload, this.hmacSecret, tokenOptions);
            } else {
                throw new Error(`Token creation not supported for ${this.allowedAlgorithm}`);
            }
        } catch (error) {
            throw new Error(`Token creation failed: ${error.message}`);
        }
    }
    
    verifyToken(token, options = {}) {
        if (!token || typeof token !== 'string') {
            throw new Error('Token must be a valid string');
        }
        
        // Validate token format
        const parts = token.split('.');
        if (parts.length !== 3) {
            throw new Error('Invalid token format');
        }
        
        // Check algorithm in header before verification
        let header;
        try {
            header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
        } catch (error) {
            throw new Error('Invalid token header');
        }
        
        // CRITICAL: Validate algorithm matches expected
        if (header.alg !== this.allowedAlgorithm) {
            throw new Error(`Algorithm mismatch. Expected ${this.allowedAlgorithm}, got ${header.alg}`);
        }
        
        // Prepare verification options
        const verifyOptions = {
            algorithms: [this.allowedAlgorithm], // Strict algorithm enforcement
            issuer: process.env.JWT_ISSUER || 'secure-app',
            audience: process.env.JWT_AUDIENCE || 'secure-app-users',
            clockTolerance: 30, // 30 seconds clock skew tolerance
            ...options
        };
        
        try {
            let verificationKey;
            
            if (this.allowedAlgorithm === 'RS256') {
                verificationKey = this.rsaPublicKey;
            } else if (this.allowedAlgorithm === 'HS256') {
                verificationKey = this.hmacSecret;
            } else {
                throw new Error(`Verification not supported for ${this.allowedAlgorithm}`);
            }
            
            const decoded = jwt.verify(token, verificationKey, verifyOptions);
            
            // Additional security validations
            this.validateTokenPayload(decoded);
            
            return {
                valid: true,
                payload: decoded,
                algorithm: this.allowedAlgorithm
            };
            
        } catch (error) {
            // Log security events without exposing details
            console.warn('JWT verification failed:', {
                error: error.name,
                timestamp: new Date().toISOString(),
                algorithm: header.alg
            });
            
            throw new Error('Token verification failed');
        }
    }
    
    validateTokenPayload(payload) {
        // Check for required claims
        const requiredClaims = ['iat', 'iss', 'aud'];
        for (const claim of requiredClaims) {
            if (!payload[claim]) {
                throw new Error(`Missing required claim: ${claim}`);
            }
        }
        
        // Validate issuer and audience
        const expectedIssuer = process.env.JWT_ISSUER || 'secure-app';
        const expectedAudience = process.env.JWT_AUDIENCE || 'secure-app-users';
        
        if (payload.iss !== expectedIssuer) {
            throw new Error('Invalid issuer');
        }
        
        if (payload.aud !== expectedAudience) {
            throw new Error('Invalid audience');
        }
        
        // Check token age (additional security)
        const maxAge = 24 * 60 * 60; // 24 hours
        const tokenAge = Math.floor(Date.now() / 1000) - payload.iat;
        
        if (tokenAge > maxAge) {
            throw new Error('Token too old');
        }
    }
    
    // Secure token refresh with algorithm consistency
    refreshToken(oldToken, newPayload = {}) {
        // Verify old token first
        const verification = this.verifyToken(oldToken);
        
        if (!verification.valid) {
            throw new Error('Cannot refresh invalid token');
        }
        
        // Create new token with same algorithm
        const refreshedPayload = {
            ...verification.payload,
            ...newPayload,
            // Remove old timing claims
            iat: undefined,
            exp: undefined
        };
        
        return this.createToken(refreshedPayload);
    }
    
    // Get token information without verification (for debugging)
    inspectToken(token) {
        try {
            const parts = token.split('.');
            const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
            const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
            
            return {
                header,
                payload,
                signature: parts[2]
            };
        } catch (error) {
            throw new Error('Cannot inspect malformed token');
        }
    }
}

// SECURE: Express middleware with strict algorithm enforcement
class SecureAuthMiddleware {
    constructor() {
        this.jwtManager = new SecureJWTManager();
        this.tokenBlacklist = new Set(); // In production, use Redis
    }
    
    authenticate() {
        return async (req, res, next) => {
            try {
                const token = this.extractToken(req);
                
                if (!token) {
                    return res.status(401).json({
                        error: 'Authentication required',
                        code: 'MISSING_TOKEN'
                    });
                }
                
                // Check token blacklist
                if (this.tokenBlacklist.has(token)) {
                    return res.status(401).json({
                        error: 'Token has been revoked',
                        code: 'REVOKED_TOKEN'
                    });
                }
                
                // Verify token with strict algorithm checking
                const verification = this.jwtManager.verifyToken(token);
                
                req.user = verification.payload;
                req.authMethod = 'jwt';
                req.tokenAlgorithm = verification.algorithm;
                
                next();
                
            } catch (error) {
                return res.status(401).json({
                    error: 'Invalid authentication token',
                    code: 'INVALID_TOKEN'
                });
            }
        };
    }
    
    extractToken(req) {
        const authHeader = req.headers.authorization;
        
        if (authHeader && authHeader.startsWith('Bearer ')) {
            return authHeader.substring(7);
        }
        
        return null;
    }
    
    revokeToken(token) {
        this.tokenBlacklist.add(token);
        
        // In production, store in Redis with TTL
        console.log('Token revoked:', {
            timestamp: new Date().toISOString(),
            tokenHash: crypto.createHash('sha256').update(token).digest('hex').substring(0, 8)
        });
    }
    
    // Authorization middleware
    authorize(requiredRoles = []) {
        return (req, res, next) => {
            if (!req.user) {
                return res.status(401).json({
                    error: 'Authentication required for authorization',
                    code: 'AUTH_REQUIRED'
                });
            }
            
            if (requiredRoles.length > 0 && !requiredRoles.includes(req.user.role)) {
                return res.status(403).json({
                    error: 'Insufficient permissions',
                    code: 'INSUFFICIENT_PERMISSIONS',
                    required: requiredRoles,
                    current: req.user.role
                });
            }
            
            next();
        };
    }
}

// Usage example with secure implementation
const express = require('express');
const app = express();

app.use(express.json());

const authMiddleware = new SecureAuthMiddleware();
const jwtManager = new SecureJWTManager();

// Secure login endpoint
app.post('/auth/login', async (req, res) => {
    const { username, password } = req.body;
    
    try {
        // Implement secure authentication logic here
        // This is a simplified example
        if (username && password) {
            const payload = {
                sub: username,
                role: username === 'admin' ? 'admin' : 'user',
                scope: ['read', 'write']
            };
            
            const token = jwtManager.createToken(payload, {
                expiresIn: '15m'
            });
            
            res.json({
                token,
                expiresIn: '15m',
                tokenType: 'Bearer'
            });
        } else {
            res.status(401).json({ error: 'Invalid credentials' });
        }
    } catch (error) {
        res.status(500).json({ error: 'Authentication service error' });
    }
});

// Secure protected routes
app.use('/api', authMiddleware.authenticate());

app.get('/api/profile', (req, res) => {
    res.json({
        user: {
            username: req.user.sub,
            role: req.user.role,
            scope: req.user.scope
        },
        tokenInfo: {
            algorithm: req.tokenAlgorithm,
            issued: new Date(req.user.iat * 1000),
            expires: new Date(req.user.exp * 1000)
        }
    });
});

app.get('/api/admin/data', 
    authMiddleware.authorize(['admin']), 
    (req, res) => {
        res.json({
            message: 'Secure admin data',
            algorithm: req.tokenAlgorithm,
            user: req.user.sub
        });
    }
);

// Secure token refresh endpoint
app.post('/auth/refresh', authMiddleware.authenticate(), (req, res) => {
    try {
        const oldToken = authMiddleware.extractToken(req);
        const newToken = jwtManager.refreshToken(oldToken);
        
        // Revoke old token
        authMiddleware.revokeToken(oldToken);
        
        res.json({
            token: newToken,
            expiresIn: '15m',
            tokenType: 'Bearer'
        });
    } catch (error) {
        res.status(400).json({ error: 'Token refresh failed' });
    }
});

app.listen(3000, () => {
    console.log('Secure JWT server running on port 3000');
    console.log('Algorithm enforced:', process.env.JWT_ALGORITHM || 'RS256');
});
2

Implement Separate Key Management for Different Algorithms

Use completely separate and properly generated keys for different JWT algorithms. Never reuse RSA public keys as HMAC secrets. Implement proper key rotation and storage practices to prevent key confusion attacks.

View implementation – JAVASCRIPT
// SECURE: Separate key management for different JWT algorithms
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

class SecureKeyManager {
    constructor() {
        this.keys = new Map();
        this.keyRotationSchedule = new Map();
        this.initializeKeys();
    }
    
    initializeKeys() {
        // Load or generate keys for different algorithms
        this.loadRSAKeys();
        this.loadHMACKeys();
        this.loadECDSAKeys();
        
        // Set up key rotation schedules
        this.scheduleKeyRotation();
    }
    
    loadRSAKeys() {
        try {
            const privateKey = fs.readFileSync('keys/rsa-private.pem', 'utf8');
            const publicKey = fs.readFileSync('keys/rsa-public.pem', 'utf8');
            
            // Validate RSA key pair
            this.validateRSAKeyPair(privateKey, publicKey);
            
            this.keys.set('RS256', {
                algorithm: 'RS256',
                privateKey: privateKey,
                publicKey: publicKey,
                keyId: this.generateKeyId('RS256'),
                createdAt: new Date(),
                usage: 'signature'
            });
            
        } catch (error) {
            console.warn('RSA keys not found, generating new pair...');
            this.generateRSAKeys();
        }
    }
    
    generateRSAKeys() {
        const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem'
            }
        });
        
        // Save keys securely
        this.ensureKeyDirectory();
        fs.writeFileSync('keys/rsa-private.pem', privateKey, { mode: 0o600 });
        fs.writeFileSync('keys/rsa-public.pem', publicKey, { mode: 0o644 });
        
        this.keys.set('RS256', {
            algorithm: 'RS256',
            privateKey: privateKey,
            publicKey: publicKey,
            keyId: this.generateKeyId('RS256'),
            createdAt: new Date(),
            usage: 'signature'
        });
        
        console.log('Generated new RSA key pair for RS256');
    }
    
    loadHMACKeys() {
        const hmacSecret = process.env.JWT_HMAC_SECRET;
        
        if (!hmacSecret) {
            console.warn('HMAC secret not found, generating new secret...');
            this.generateHMACKey();
            return;
        }
        
        // Validate HMAC secret strength
        if (hmacSecret.length < 32) {
            throw new Error('HMAC secret must be at least 32 bytes (256 bits)');
        }
        
        this.keys.set('HS256', {
            algorithm: 'HS256',
            secret: hmacSecret,
            keyId: this.generateKeyId('HS256'),
            createdAt: new Date(),
            usage: 'hmac'
        });
    }
    
    generateHMACKey() {
        // Generate cryptographically secure random key
        const secret = crypto.randomBytes(64).toString('base64'); // 512 bits
        
        this.keys.set('HS256', {
            algorithm: 'HS256',
            secret: secret,
            keyId: this.generateKeyId('HS256'),
            createdAt: new Date(),
            usage: 'hmac'
        });
        
        console.log('Generated new HMAC secret for HS256');
        console.log('Please set JWT_HMAC_SECRET environment variable to:', secret);
    }
    
    loadECDSAKeys() {
        try {
            const privateKey = fs.readFileSync('keys/ecdsa-private.pem', 'utf8');
            const publicKey = fs.readFileSync('keys/ecdsa-public.pem', 'utf8');
            
            this.keys.set('ES256', {
                algorithm: 'ES256',
                privateKey: privateKey,
                publicKey: publicKey,
                keyId: this.generateKeyId('ES256'),
                createdAt: new Date(),
                usage: 'signature'
            });
            
        } catch (error) {
            console.warn('ECDSA keys not found, generating new pair...');
            this.generateECDSAKeys();
        }
    }
    
    generateECDSAKeys() {
        const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
            namedCurve: 'prime256v1', // P-256 curve for ES256
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem'
            }
        });
        
        this.ensureKeyDirectory();
        fs.writeFileSync('keys/ecdsa-private.pem', privateKey, { mode: 0o600 });
        fs.writeFileSync('keys/ecdsa-public.pem', publicKey, { mode: 0o644 });
        
        this.keys.set('ES256', {
            algorithm: 'ES256',
            privateKey: privateKey,
            publicKey: publicKey,
            keyId: this.generateKeyId('ES256'),
            createdAt: new Date(),
            usage: 'signature'
        });
        
        console.log('Generated new ECDSA key pair for ES256');
    }
    
    validateRSAKeyPair(privateKey, publicKey) {
        try {
            // Test signing and verification
            const testData = 'test-data-for-validation';
            const signer = crypto.createSign('RSA-SHA256');
            signer.update(testData);
            const signature = signer.sign(privateKey);
            
            const verifier = crypto.createVerify('RSA-SHA256');
            verifier.update(testData);
            const isValid = verifier.verify(publicKey, signature);
            
            if (!isValid) {
                throw new Error('RSA key pair validation failed');
            }
        } catch (error) {
            throw new Error(`Invalid RSA key pair: ${error.message}`);
        }
    }
    
    generateKeyId(algorithm) {
        const timestamp = Date.now().toString(36);
        const random = crypto.randomBytes(4).toString('hex');
        return `${algorithm.toLowerCase()}_${timestamp}_${random}`;
    }
    
    ensureKeyDirectory() {
        const keyDir = 'keys';
        if (!fs.existsSync(keyDir)) {
            fs.mkdirSync(keyDir, { mode: 0o700 });
        }
    }
    
    getSigningKey(algorithm) {
        const keyData = this.keys.get(algorithm);
        if (!keyData) {
            throw new Error(`No signing key available for algorithm: ${algorithm}`);
        }
        
        switch (algorithm) {
            case 'RS256':
            case 'ES256':
                return keyData.privateKey;
            case 'HS256':
                return keyData.secret;
            default:
                throw new Error(`Unsupported algorithm for signing: ${algorithm}`);
        }
    }
    
    getVerificationKey(algorithm) {
        const keyData = this.keys.get(algorithm);
        if (!keyData) {
            throw new Error(`No verification key available for algorithm: ${algorithm}`);
        }
        
        switch (algorithm) {
            case 'RS256':
            case 'ES256':
                return keyData.publicKey;
            case 'HS256':
                return keyData.secret;
            default:
                throw new Error(`Unsupported algorithm for verification: ${algorithm}`);
        }
    }
    
    getKeyId(algorithm) {
        const keyData = this.keys.get(algorithm);
        return keyData ? keyData.keyId : null;
    }
    
    // Key rotation functionality
    scheduleKeyRotation() {
        const rotationInterval = 30 * 24 * 60 * 60 * 1000; // 30 days
        
        setInterval(() => {
            this.rotateKeys();
        }, rotationInterval);
        
        console.log('Key rotation scheduled every 30 days');
    }
    
    async rotateKeys() {
        console.log('Starting key rotation...');
        
        // Create backup of current keys
        this.backupCurrentKeys();
        
        // Generate new keys
        this.generateRSAKeys();
        this.generateHMACKey();
        this.generateECDSAKeys();
        
        console.log('Key rotation completed');
    }
    
    backupCurrentKeys() {
        const backupDir = `keys/backup-${Date.now()}`;
        fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 });
        
        // Backup existing key files
        const keyFiles = ['rsa-private.pem', 'rsa-public.pem', 'ecdsa-private.pem', 'ecdsa-public.pem'];
        
        for (const file of keyFiles) {
            const sourcePath = path.join('keys', file);
            const backupPath = path.join(backupDir, file);
            
            if (fs.existsSync(sourcePath)) {
                fs.copyFileSync(sourcePath, backupPath);
            }
        }
        
        console.log(`Keys backed up to ${backupDir}`);
    }
    
    // Get all available algorithms
    getSupportedAlgorithms() {
        return Array.from(this.keys.keys());
    }
    
    // Get key metadata
    getKeyMetadata(algorithm) {
        const keyData = this.keys.get(algorithm);
        if (!keyData) {
            return null;
        }
        
        return {
            algorithm: keyData.algorithm,
            keyId: keyData.keyId,
            createdAt: keyData.createdAt,
            usage: keyData.usage,
            age: Date.now() - keyData.createdAt.getTime()
        };
    }
    
    // Security audit of keys
    auditKeys() {
        const auditResults = [];
        
        for (const [algorithm, keyData] of this.keys) {
            const age = Date.now() - keyData.createdAt.getTime();
            const maxAge = 90 * 24 * 60 * 60 * 1000; // 90 days
            
            auditResults.push({
                algorithm,
                keyId: keyData.keyId,
                age: Math.floor(age / (24 * 60 * 60 * 1000)), // days
                status: age > maxAge ? 'EXPIRED' : 'VALID',
                recommendation: age > maxAge ? 'Rotate key immediately' : 'No action needed'
            });
        }
        
        return auditResults;
    }
}

// SECURE: JWT manager using separate key management
class SecureAlgorithmJWTManager {
    constructor() {
        this.keyManager = new SecureKeyManager();
        this.enforceAlgorithm = process.env.JWT_ENFORCE_ALGORITHM || 'RS256';
        
        // Validate that the enforced algorithm is supported
        if (!this.keyManager.getSupportedAlgorithms().includes(this.enforceAlgorithm)) {
            throw new Error(`Enforced algorithm ${this.enforceAlgorithm} is not supported`);
        }
    }
    
    createToken(payload, options = {}) {
        const algorithm = options.algorithm || this.enforceAlgorithm;
        
        // Strict algorithm enforcement
        if (algorithm !== this.enforceAlgorithm) {
            throw new Error(`Algorithm ${algorithm} not allowed. Only ${this.enforceAlgorithm} is permitted.`);
        }
        
        const signingKey = this.keyManager.getSigningKey(algorithm);
        const keyId = this.keyManager.getKeyId(algorithm);
        
        const tokenOptions = {
            algorithm: algorithm,
            keyid: keyId,
            expiresIn: options.expiresIn || '15m',
            issuer: 'secure-jwt-service',
            audience: 'secure-app'
        };
        
        return jwt.sign(payload, signingKey, tokenOptions);
    }
    
    verifyToken(token) {
        // Parse header to check algorithm
        const header = this.parseTokenHeader(token);
        
        // Strict algorithm validation
        if (header.alg !== this.enforceAlgorithm) {
            throw new Error(`Algorithm ${header.alg} is not allowed. Expected ${this.enforceAlgorithm}.`);
        }
        
        // Get appropriate verification key
        const verificationKey = this.keyManager.getVerificationKey(header.alg);
        
        const verifyOptions = {
            algorithms: [header.alg], // Only allow the expected algorithm
            issuer: 'secure-jwt-service',
            audience: 'secure-app'
        };
        
        return jwt.verify(token, verificationKey, verifyOptions);
    }
    
    parseTokenHeader(token) {
        try {
            const headerB64 = token.split('.')[0];
            return JSON.parse(Buffer.from(headerB64, 'base64').toString());
        } catch (error) {
            throw new Error('Invalid token format');
        }
    }
    
    getKeyAuditReport() {
        return this.keyManager.auditKeys();
    }
}

// Usage example
const secureJWT = new SecureAlgorithmJWTManager();

// This will work - using enforced algorithm
const validToken = secureJWT.createToken({
    sub: 'user123',
    role: 'user'
});

// This will throw an error - algorithm confusion prevented
try {
    const maliciousToken = secureJWT.createToken({
        sub: 'admin',
        role: 'admin'
    }, { algorithm: 'HS256' }); // Will fail if RS256 is enforced
} catch (error) {
    console.log('Algorithm confusion prevented:', error.message);
}

// Verification with strict algorithm checking
try {
    const decoded = secureJWT.verifyToken(validToken);
    console.log('Token verified successfully:', decoded);
} catch (error) {
    console.error('Token verification failed:', error.message);
}
3

Implement Runtime Algorithm Validation and Monitoring

Deploy comprehensive runtime validation that continuously monitors JWT algorithm usage, detects algorithm confusion attempts, and provides real-time security alerting. This includes logging suspicious token patterns and implementing automated response mechanisms.

View implementation – JAVASCRIPT
// SECURE: Runtime algorithm validation and monitoring system
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const EventEmitter = require('events');

class JWTSecurityMonitor extends EventEmitter {
    constructor() {
        super();
        this.suspiciousAttempts = new Map();
        this.algorithmStats = new Map();
        this.blockedTokens = new Set();
        this.alertThresholds = {
            algorithmMismatch: 5,
            timeWindow: 60000, // 1 minute
            maxSuspiciousAttempts: 10
        };
        
        this.setupEventHandlers();
    }
    
    setupEventHandlers() {
        this.on('algorithm_mismatch', this.handleAlgorithmMismatch.bind(this));
        this.on('suspicious_pattern', this.handleSuspiciousPattern.bind(this));
        this.on('security_alert', this.handleSecurityAlert.bind(this));
    }
    
    validateTokenAlgorithm(token, expectedAlgorithm) {
        const validation = {
            isValid: false,
            algorithm: null,
            warnings: [],
            errors: [],
            securityScore: 0
        };
        
        try {
            // Parse token header without verification
            const parts = token.split('.');
            if (parts.length !== 3) {
                validation.errors.push('Invalid token format');
                return validation;
            }
            
            const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
            validation.algorithm = header.alg;
            
            // Critical: Check for algorithm confusion
            if (header.alg !== expectedAlgorithm) {
                validation.errors.push(`Algorithm mismatch: expected ${expectedAlgorithm}, got ${header.alg}`);
                
                this.emit('algorithm_mismatch', {
                    expected: expectedAlgorithm,
                    actual: header.alg,
                    token: this.hashToken(token),
                    timestamp: new Date()
                });
                
                return validation;
            }
            
            // Check for dangerous algorithm combinations
            this.checkDangerousAlgorithms(header.alg, validation);
            
            // Check for suspicious patterns
            this.checkSuspiciousPatterns(token, header, validation);
            
            // Update statistics
            this.updateAlgorithmStats(header.alg);
            
            validation.isValid = validation.errors.length === 0;
            validation.securityScore = this.calculateSecurityScore(validation);
            
            return validation;
            
        } catch (error) {
            validation.errors.push(`Token parsing failed: ${error.message}`);
            return validation;
        }
    }
    
    checkDangerousAlgorithms(algorithm, validation) {
        const dangerousAlgorithms = ['none', 'HS256'];
        const weakAlgorithms = ['HS1', 'RS1'];
        
        if (algorithm === 'none') {
            validation.errors.push('Algorithm "none" is not allowed');
            validation.securityScore -= 100;
        }
        
        if (weakAlgorithms.includes(algorithm)) {
            validation.warnings.push(`Weak algorithm detected: ${algorithm}`);
            validation.securityScore -= 50;
        }
        
        // Check for algorithm confusion indicators
        if (algorithm === 'HS256') {
            validation.warnings.push('HMAC algorithm detected - ensure proper key management');
            validation.securityScore -= 10;
        }
    }
    
    checkSuspiciousPatterns(token, header, validation) {
        const tokenHash = this.hashToken(token);
        
        // Check for repeated suspicious attempts
        if (this.suspiciousAttempts.has(tokenHash)) {
            const attempts = this.suspiciousAttempts.get(tokenHash);
            attempts.count++;
            attempts.lastSeen = new Date();
            
            if (attempts.count > this.alertThresholds.maxSuspiciousAttempts) {
                validation.errors.push('Token flagged as suspicious due to repeated attempts');
                this.blockedTokens.add(tokenHash);
                
                this.emit('security_alert', {
                    type: 'REPEATED_SUSPICIOUS_TOKEN',
                    tokenHash,
                    attempts: attempts.count,
                    severity: 'HIGH'
                });
            }
        }
        
        // Check for algorithm switching patterns
        this.checkAlgorithmSwitchingPattern(header.alg, tokenHash, validation);
        
        // Check for timing-based attacks
        this.checkTimingAttackPattern(token, validation);
        
        // Check for payload suspicious patterns
        this.checkPayloadPatterns(token, validation);
    }
    
    checkAlgorithmSwitchingPattern(algorithm, tokenHash, validation) {
        const key = `switching_${tokenHash.substring(0, 16)}`;
        
        if (!this.suspiciousAttempts.has(key)) {
            this.suspiciousAttempts.set(key, {
                algorithms: new Set([algorithm]),
                count: 1,
                firstSeen: new Date(),
                lastSeen: new Date()
            });
        } else {
            const pattern = this.suspiciousAttempts.get(key);
            pattern.algorithms.add(algorithm);
            pattern.count++;
            pattern.lastSeen = new Date();
            
            // Alert if multiple algorithms used for similar tokens
            if (pattern.algorithms.size > 1) {
                validation.warnings.push('Algorithm switching pattern detected');
                validation.securityScore -= 25;
                
                this.emit('suspicious_pattern', {
                    type: 'ALGORITHM_SWITCHING',
                    algorithms: Array.from(pattern.algorithms),
                    attempts: pattern.count,
                    timespan: pattern.lastSeen - pattern.firstSeen
                });
            }
        }
    }
    
    checkTimingAttackPattern(token, validation) {
        const now = Date.now();
        const timingKey = `timing_${this.hashToken(token).substring(0, 8)}`;
        
        if (!this.suspiciousAttempts.has(timingKey)) {
            this.suspiciousAttempts.set(timingKey, {
                attempts: [],
                count: 1
            });
        } else {
            const timing = this.suspiciousAttempts.get(timingKey);
            timing.attempts.push(now);
            timing.count++;
            
            // Check for rapid successive attempts (potential brute force)
            const recentAttempts = timing.attempts.filter(
                time => now - time < this.alertThresholds.timeWindow
            );
            
            if (recentAttempts.length > this.alertThresholds.algorithmMismatch) {
                validation.warnings.push('Rapid token validation attempts detected');
                validation.securityScore -= 15;
                
                this.emit('suspicious_pattern', {
                    type: 'RAPID_ATTEMPTS',
                    attempts: recentAttempts.length,
                    timeWindow: this.alertThresholds.timeWindow
                });
            }
        }
    }
    
    checkPayloadPatterns(token, validation) {
        try {
            const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
            
            // Check for suspicious privilege escalation
            if (payload.role === 'admin' || payload.role === 'superadmin') {
                validation.warnings.push('High-privilege role detected in token');
                validation.securityScore -= 20;
            }
            
            // Check for unusual expiration times
            if (payload.exp) {
                const expirationTime = payload.exp * 1000;
                const currentTime = Date.now();
                const duration = expirationTime - currentTime;
                
                // Very long expiration (>24 hours)
                if (duration > 24 * 60 * 60 * 1000) {
                    validation.warnings.push('Unusually long token expiration detected');
                    validation.securityScore -= 10;
                }
                
                // Already expired tokens being attempted
                if (duration < 0) {
                    validation.errors.push('Expired token attempted');
                    validation.securityScore -= 30;
                }
            }
            
            // Check for missing security claims
            const requiredClaims = ['iat', 'exp', 'iss', 'aud'];
            const missingClaims = requiredClaims.filter(claim => !payload[claim]);
            
            if (missingClaims.length > 0) {
                validation.warnings.push(`Missing security claims: ${missingClaims.join(', ')}`);
                validation.securityScore -= missingClaims.length * 5;
            }
            
        } catch (error) {
            validation.warnings.push('Could not parse token payload for security analysis');
        }
    }
    
    calculateSecurityScore(validation) {
        let score = 100; // Start with perfect score
        
        // Deduct points for errors and warnings
        score -= validation.errors.length * 50;
        score -= validation.warnings.length * 10;
        
        // Ensure score doesn't go below 0
        return Math.max(0, score);
    }
    
    hashToken(token) {
        return crypto.createHash('sha256').update(token).digest('hex');
    }
    
    updateAlgorithmStats(algorithm) {
        if (!this.algorithmStats.has(algorithm)) {
            this.algorithmStats.set(algorithm, {
                count: 0,
                firstSeen: new Date(),
                lastSeen: new Date()
            });
        }
        
        const stats = this.algorithmStats.get(algorithm);
        stats.count++;
        stats.lastSeen = new Date();
    }
    
    handleAlgorithmMismatch(event) {
        console.warn('SECURITY ALERT: Algorithm mismatch detected', {
            expected: event.expected,
            actual: event.actual,
            tokenHash: event.token,
            timestamp: event.timestamp
        });
        
        // In production, integrate with SIEM/monitoring system
        this.recordSecurityEvent('ALGORITHM_MISMATCH', event);
    }
    
    handleSuspiciousPattern(event) {
        console.warn('SECURITY WARNING: Suspicious pattern detected', event);
        
        // Escalate if pattern indicates serious attack
        if (event.type === 'ALGORITHM_SWITCHING' && event.algorithms.includes('HS256')) {
            this.emit('security_alert', {
                type: 'POTENTIAL_ALGORITHM_CONFUSION_ATTACK',
                details: event,
                severity: 'CRITICAL'
            });
        }
    }
    
    handleSecurityAlert(event) {
        console.error('CRITICAL SECURITY ALERT:', event);
        
        // In production, implement:
        // - Send to security team
        // - Update WAF rules
        // - Trigger incident response
        // - Block malicious IPs
        
        this.recordSecurityEvent('SECURITY_ALERT', event);
    }
    
    recordSecurityEvent(type, event) {
        // In production, integrate with security logging system
        const securityLog = {
            timestamp: new Date().toISOString(),
            type: type,
            event: event,
            severity: event.severity || 'MEDIUM',
            source: 'jwt-security-monitor'
        };
        
        // Log to security audit trail
        console.log('SECURITY_LOG:', JSON.stringify(securityLog));
    }
    
    getSecurityReport() {
        return {
            algorithmStats: Object.fromEntries(this.algorithmStats),
            suspiciousAttempts: this.suspiciousAttempts.size,
            blockedTokens: this.blockedTokens.size,
            alertThresholds: this.alertThresholds,
            reportGeneratedAt: new Date().toISOString()
        };
    }
    
    // Clean up old tracking data
    cleanup() {
        const now = Date.now();
        const maxAge = 24 * 60 * 60 * 1000; // 24 hours
        
        for (const [key, data] of this.suspiciousAttempts) {
            if (now - data.lastSeen.getTime() > maxAge) {
                this.suspiciousAttempts.delete(key);
            }
        }
    }
}

// SECURE: JWT validator with comprehensive security monitoring
class SecureJWTValidator {
    constructor() {
        this.securityMonitor = new JWTSecurityMonitor();
        this.enforceAlgorithm = 'RS256';
        this.publicKey = this.loadPublicKey();
        
        // Set up periodic cleanup
        setInterval(() => {
            this.securityMonitor.cleanup();
        }, 60 * 60 * 1000); // Every hour
    }
    
    loadPublicKey() {
        // Load your RSA public key
        return fs.readFileSync('keys/rsa-public.pem', 'utf8');
    }
    
    validateAndVerifyToken(token) {
        // First, perform security validation
        const securityValidation = this.securityMonitor.validateTokenAlgorithm(
            token, 
            this.enforceAlgorithm
        );
        
        if (!securityValidation.isValid) {
            throw new Error(`Security validation failed: ${securityValidation.errors.join(', ')}`);
        }
        
        // Check if token is blocked
        const tokenHash = this.securityMonitor.hashToken(token);
        if (this.securityMonitor.blockedTokens.has(tokenHash)) {
            throw new Error('Token has been blocked due to security concerns');
        }
        
        // Proceed with JWT verification
        try {
            const decoded = jwt.verify(token, this.publicKey, {
                algorithms: [this.enforceAlgorithm],
                issuer: 'secure-jwt-service',
                audience: 'secure-app'
            });
            
            // Additional security checks on verified payload
            this.performPostVerificationChecks(decoded, securityValidation);
            
            return {
                payload: decoded,
                securityScore: securityValidation.securityScore,
                warnings: securityValidation.warnings
            };
            
        } catch (error) {
            // Log verification failure for security analysis
            this.securityMonitor.emit('verification_failure', {
                error: error.message,
                tokenHash: tokenHash,
                securityScore: securityValidation.securityScore
            });
            
            throw new Error('Token verification failed');
        }
    }
    
    performPostVerificationChecks(payload, securityValidation) {
        // Check for privilege escalation attempts
        if (payload.role === 'admin' && securityValidation.securityScore < 80) {
            this.securityMonitor.emit('security_alert', {
                type: 'SUSPICIOUS_ADMIN_TOKEN',
                payload: { role: payload.role, sub: payload.sub },
                securityScore: securityValidation.securityScore,
                severity: 'HIGH'
            });
        }
        
        // Check for unusual token characteristics
        if (payload.exp - payload.iat > 24 * 60 * 60) { // >24 hours
            this.securityMonitor.emit('suspicious_pattern', {
                type: 'LONG_LIVED_TOKEN',
                duration: payload.exp - payload.iat,
                subject: payload.sub
            });
        }
    }
    
    getSecurityReport() {
        return this.securityMonitor.getSecurityReport();
    }
}

// Usage example with Express middleware
const express = require('express');
const app = express();

const jwtValidator = new SecureJWTValidator();

// Secure authentication middleware
function secureJWTMiddleware(req, res, next) {
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    if (!token) {
        return res.status(401).json({ error: 'No token provided' });
    }
    
    try {
        const validation = jwtValidator.validateAndVerifyToken(token);
        
        req.user = validation.payload;
        req.securityInfo = {
            score: validation.securityScore,
            warnings: validation.warnings
        };
        
        // Log successful validation with security score
        if (validation.securityScore < 70) {
            console.warn('Low security score token accepted:', {
                score: validation.securityScore,
                user: validation.payload.sub,
                warnings: validation.warnings
            });
        }
        
        next();
        
    } catch (error) {
        return res.status(401).json({
            error: 'Authentication failed',
            message: error.message
        });
    }
}

// Apply middleware
app.use('/api', secureJWTMiddleware);

// Protected route
app.get('/api/profile', (req, res) => {
    res.json({
        user: req.user,
        securityInfo: req.securityInfo
    });
});

// Security monitoring endpoint
app.get('/api/security/report', (req, res) => {
    // Only allow admin access
    if (req.user.role !== 'admin') {
        return res.status(403).json({ error: 'Admin required' });
    }
    
    res.json(jwtValidator.getSecurityReport());
});

app.listen(3000, () => {
    console.log('Secure JWT service with monitoring running on port 3000');
});

Detect This Vulnerability in Your Code

Sourcery automatically identifies jwt algorithm confusion attack (rs256 vs hs256) and many other security issues in your codebase.