Vulnerability
Flask

JWT Vulnerabilities in Flask

Flask applications use PyJWT directly or through Flask-JWT-Extended for token authentication. The most critical vulnerability is calling jwt.decode() without specifying algorithms, enabling algorithm confusion attacks. Flask's app.secret_key is often used as the JWT secret, creating a single point of failure when it is weak or exposed.

Scan Your Flask App

How JWT Vulnerabilities Manifests in Flask

JWT vulnerabilities in Flask appear in several patterns: Direct PyJWT usage without algorithm specification: jwt.decode(token, secret) without the algorithms parameter allows algorithm confusion. Older versions of PyJWT did not require the algorithms parameter at all, and many tutorials show the vulnerable pattern. Using Flask's app.secret_key as the JWT secret ties JWT security to Flask's session security. If app.secret_key is set to a weak value like "dev" or "changeme", JWTs can be forged. Flask-JWT-Extended configurations that set JWT_ACCESS_TOKEN_EXPIRES to very long durations (days or weeks) combined with no token revocation mechanism mean stolen tokens persist. Custom token validation in Flask decorators that check only the signature but not expiration, issuer, or audience claims allow expired or cross-service tokens to be accepted.

Real-World Impact

A Flask API used jwt.decode(token, app.secret_key) without specifying algorithms. The app.secret_key was the default "dev" from a tutorial. An attacker brute-forced the secret in seconds and forged admin tokens, gaining full access to the API including user management and data export endpoints. A Flask microservice architecture shared JWT secrets between services for convenience. When one low-priority service was compromised through an unrelated vulnerability, the attacker used the shared secret to forge tokens accepted by all services, including the payment processing service.

Step-by-Step Fix

1

Fix PyJWT algorithm confusion

Always pass the algorithms parameter when decoding JWTs with PyJWT.

import jwt
import os

JWT_SECRET = os.environ['JWT_SECRET']  # Strong random secret

# VULNERABLE
payload = jwt.decode(token, JWT_SECRET)

# SECURE
payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])

# For encoding, also specify the algorithm
token = jwt.encode(
    {
        'sub': user_id,
        'exp': datetime.utcnow() + timedelta(minutes=15),
        'iat': datetime.utcnow(),
        'iss': 'your-flask-app',
    },
    JWT_SECRET,
    algorithm='HS256'
)
2

Configure Flask-JWT-Extended securely

Set up Flask-JWT-Extended with a dedicated secret, short token lifetimes, and proper configuration.

from flask import Flask
from flask_jwt_extended import JWTManager
from datetime import timedelta
import os

app = Flask(__name__)

# Use a dedicated JWT secret, NOT app.secret_key
app.config['JWT_SECRET_KEY'] = os.environ['JWT_SECRET_KEY']
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=15)
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=1)
app.config['JWT_ALGORITHM'] = 'HS256'
app.config['JWT_TOKEN_LOCATION'] = ['cookies']  # Not headers
app.config['JWT_COOKIE_SECURE'] = True
app.config['JWT_COOKIE_HTTPONLY'] = True
app.config['JWT_COOKIE_SAMESITE'] = 'Lax'
app.config['JWT_COOKIE_CSRF_PROTECT'] = True

jwt_manager = JWTManager(app)
3

Implement token revocation with Flask-JWT-Extended

Enable token blacklisting so tokens can be revoked on logout and password change.

from flask_jwt_extended import (
    create_access_token, create_refresh_token,
    jwt_required, get_jwt, get_jwt_identity
)

# In-memory blocklist (use Redis in production)
BLOCKLIST = set()

@jwt_manager.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload):
    jti = jwt_payload['jti']
    return jti in BLOCKLIST

@app.route('/logout', methods=['POST'])
@jwt_required()
def logout():
    jti = get_jwt()['jti']
    BLOCKLIST.add(jti)
    return jsonify({'message': 'Logged out'})

@app.route('/change-password', methods=['POST'])
@jwt_required()
def change_password():
    # ... validate and change password ...
    # Revoke current token
    jti = get_jwt()['jti']
    BLOCKLIST.add(jti)
    return jsonify({'message': 'Password changed. Please log in again.'})

Prevention Best Practices

1. Always specify algorithms when calling jwt.decode(): jwt.decode(token, secret, algorithms=['HS256']). 2. Use a strong, random secret dedicated to JWT signing — not app.secret_key. 3. Set short ACCESS_TOKEN_EXPIRES in Flask-JWT-Extended (15-30 minutes). 4. Enable token blacklisting for logout and password change flows. 5. Use different JWT secrets for different microservices. 6. Validate all standard claims: exp, iss, aud.

How to Test

1. Search your codebase for jwt.decode() calls and check if algorithms is specified. 2. Check if JWT_SECRET_KEY is different from app.secret_key and is sufficiently strong. 3. Test with an expired token — verify it is rejected. 4. Change a user password and test if the old token still works. 5. Use Vibe App Scanner to detect JWT configuration issues in your Flask application.

Frequently Asked Questions

Does PyJWT prevent algorithm confusion by default?

In PyJWT 2.0+, the algorithms parameter is required in jwt.decode(), which prevents algorithm confusion. In older versions (1.x), it was optional and defaulted to accepting any algorithm. If you are on PyJWT 2.x, you will get a DecodeError if you omit algorithms. However, some Flask extensions or legacy code may suppress this check.

Should I use Flask-JWT-Extended or raw PyJWT?

Use Flask-JWT-Extended for most applications. It handles token storage in cookies with CSRF protection, token blacklisting, refresh token rotation, and integrates cleanly with Flask decorators. Raw PyJWT requires you to implement all of these features yourself, increasing the chance of security mistakes.

How do I generate a strong JWT secret for Flask?

Use Python's secrets module: python -c "import secrets; print(secrets.token_hex(32))". This generates a 64-character hex string (256 bits of randomness). Store it in an environment variable, never in source code. Each environment (development, staging, production) should have a different secret.

Is Your Flask App Vulnerable to JWT Vulnerabilities?

VAS automatically scans for jwt vulnerabilities vulnerabilities in Flask applications and provides step-by-step remediation guidance with code examples.

Scans from $5, results in minutes. Get actionable fixes tailored to your Flask stack.