Python JWT Unverified Token Decode Vulnerability

Critical Risk Authentication Bypass
JWTPythonAuthenticationAuthorizationToken SecuritySignature VerificationSecurity Bypass

What it is

Application decodes JWT tokens without proper signature verification, allowing attackers to forge tokens and bypass authentication or authorization controls.

import jwt from flask import Flask, request @app.route('/protected') def protected_route(): # Vulnerable: No signature verification token = request.headers.get('Authorization') if token: try: # Dangerous: verify=False allows forged tokens payload = jwt.decode(token, verify=False) user_id = payload.get('user_id') return f'Welcome user {user_id}' except: pass return 'Unauthorized', 401 @app.route('/admin') def admin_route(): # Vulnerable: No algorithm specification token = request.headers.get('Authorization') if token: try: # Dangerous: algorithm not specified, can use 'none' payload = jwt.decode(token, 'secret') if payload.get('role') == 'admin': return 'Admin access granted' except: pass return 'Access denied', 403 # Vulnerable: Decoding without any verification def get_user_from_token(token): return jwt.decode(token, options={"verify_signature": False})
import jwt from flask import Flask, request from datetime import datetime, timezone # Secure: Use strong secret key JWT_SECRET = 'your-256-bit-secret-key' JWT_ALGORITHM = 'HS256' @app.route('/protected') def protected_route(): # Secure: Proper signature verification token = request.headers.get('Authorization') if token and token.startswith('Bearer '): token = token[7:] # Remove 'Bearer ' prefix try: # Secure: Verify signature and algorithm payload = jwt.decode( token, JWT_SECRET, algorithms=[JWT_ALGORITHM] ) # Validate expiration exp = payload.get('exp') if exp and datetime.fromtimestamp(exp, timezone.utc) < datetime.now(timezone.utc): return 'Token expired', 401 user_id = payload.get('user_id') return f'Welcome user {user_id}' except jwt.InvalidTokenError as e: return f'Invalid token: {str(e)}', 401 return 'Unauthorized', 401 @app.route('/admin') def admin_route(): # Secure: Full validation with all checks token = request.headers.get('Authorization') if token and token.startswith('Bearer '): token = token[7:] try: # Secure: Comprehensive validation payload = jwt.decode( token, JWT_SECRET, algorithms=[JWT_ALGORITHM], options={ "verify_signature": True, "verify_exp": True, "verify_iat": True, "require": ["exp", "iat", "user_id", "role"] } ) if payload.get('role') == 'admin': return 'Admin access granted' else: return 'Insufficient privileges', 403 except jwt.InvalidTokenError as e: return f'Invalid token: {str(e)}', 401 return 'Access denied', 403 # Secure: Proper token validation function def get_user_from_token(token): try: payload = jwt.decode( token, JWT_SECRET, algorithms=[JWT_ALGORITHM] ) return payload.get('user_id') except jwt.InvalidTokenError: return None

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Code disables signature verification: jwt.decode(token, options={'verify_signature': False}). Accepts any token without validating signature. Attackers forge tokens with arbitrary claims, impersonate users, escalate privileges. Common during debugging or when developers misunderstand JWT security model.

Root causes

Using jwt.decode() with verify=False or options={'verify_signature': False}

Code disables signature verification: jwt.decode(token, options={'verify_signature': False}). Accepts any token without validating signature. Attackers forge tokens with arbitrary claims, impersonate users, escalate privileges. Common during debugging or when developers misunderstand JWT security model.

Not Specifying algorithms Parameter in jwt.decode()

Decoding without algorithm restriction: jwt.decode(token, secret). Old PyJWT versions allowed 'none' algorithm. Even with verification, missing algorithms parameter accepts any algorithm. Attackers change algorithm to 'none' or use weak algorithms. Unrestricted algorithms enable signature bypass.

Using Weak or Predictable Secret Keys for JWT Signing

Hard-coded or weak secrets: jwt.encode(payload, 'secret123', algorithm='HS256'). Weak secrets vulnerable to brute force. Attackers crack secret, forge valid tokens. Common keys like 'secret', 'password', or company name easily guessable. Insufficient entropy in key generation compromises all tokens.

Accepting Tokens Without Validating Claims (exp, aud, iss)

Only verifying signature without claim validation: jwt.decode(token, secret, algorithms=['HS256']). Missing expiration (exp), audience (aud), issuer (iss) checks. Expired tokens accepted. Tokens for different services usable. No issuer verification allows token reuse from unrelated systems.

Using Algorithm 'none' or HS256 with Public Key Infrastructure

Accepting 'none' algorithm: if not algorithms parameter specified or algorithms=['HS256', 'none']. Algorithm 'none' provides no security. Using HS256 (symmetric) when RS256 (asymmetric) appropriate. Public/private key systems should use RS256. Symmetric keys require sharing secret, increasing compromise risk.

Fixes

1

Always Verify Signature with algorithms Parameter Specified

Use jwt.decode() with explicit algorithm: jwt.decode(token, secret, algorithms=['HS256']). Never use verify=False or disable signature verification. Specify allowed algorithms explicitly. Use algorithms=['RS256'] for asymmetric keys. Signature verification ensures token authenticity and prevents forgery.

2

Generate Strong Secret Keys with Sufficient Entropy

Use cryptographically secure secrets: import secrets; SECRET_KEY = secrets.token_urlsafe(32). Minimum 256 bits (32 bytes) for HS256. Store in environment variables: os.environ['JWT_SECRET']. Never hard-code secrets. Use key management systems for production. Rotate keys periodically.

3

Validate All Standard Claims (exp, aud, iss, nbf)

Verify claims during decode: jwt.decode(token, secret, algorithms=['HS256'], audience='myapp', issuer='api.example.com'). Check expiration automatically. Set audience to application identifier. Validate issuer matches expected value. Use options={'require': ['exp', 'aud', 'iss']} to enforce claim presence.

4

Use RS256 Algorithm for Public/Private Key Scenarios

For distributed systems, use asymmetric keys: jwt.encode(payload, private_key, algorithm='RS256'); jwt.decode(token, public_key, algorithms=['RS256']). Private key signs, public key verifies. No shared secret. Prevents key compromise from verification-only services. Better for microservices architectures.

5

Implement Token Refresh and Short Expiration Times

Set short expiration: payload = {'exp': datetime.utcnow() + timedelta(minutes=15), 'data': data}. Use refresh tokens for extended sessions. Short-lived access tokens limit exposure. Include jti (JWT ID) for token revocation tracking. Store issued tokens for invalidation capabilities.

6

Use Well-Tested JWT Libraries with Latest Security Updates

Use PyJWT latest version: pip install PyJWT>=2.8.0. Stay updated with security patches. Consider pyjwt[crypto] for additional algorithms. Avoid custom JWT implementations. Review CVEs for JWT libraries. Modern versions have secure defaults and better API preventing misuse.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python jwt unverified token decode vulnerability and many other security issues in your codebase.