[Python] JWT Token Expiration Handling Causing 500 Error in Flask-JWT-Extended and...

Discussão em 'Python' iniciado por Stack, Abril 1, 2025.

  1. Stack

    Stack Membro Participativo

    Problem: I'm building a Flask backend using flask-restful, flask-jwt-extended, and PostgreSQL. When testing JWT token expiration via Postman, expired tokens consistently result in a 500 Internal Server Error instead of a clean 401 Unauthorized response.

    Desired Behavior: When a JWT token expires, my API should gracefully return a JSON response: {"message": "Token has expired"}

    Currently, an expired token results in this error: {"message": "Internal Server Error"}

    Server Logs Traceback: jwt.exceptions.ExpiredSignatureError: Signature has expired


    Relevent Code: Flask App Initialization (init.py)

    from flask import Flask, jsonify
    from flask_jwt_extended import JWTManager
    from flask_restful import Api
    from flask_cors import CORS
    from flask_sqlalchemy import SQLAlchemy
    from flask_migrate import Migrate
    import os
    from dotenv import load_dotenv

    load_dotenv()

    app = Flask(__name__)
    CORS(app, supports_credentials=True)

    app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL')
    app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', 'temporary_secret')

    jwt = JWTManager(app)
    db = SQLAlchemy(app)
    api = Api(app)
    migrate = Migrate(app, db)

    # JWT error handlers
    @jwt.expired_token_loader
    def expired_token_callback(jwt_header, jwt_payload):
    return jsonify({"message": "Token has expired"}), 401

    @jwt.invalid_token_loader
    def invalid_token_callback(error):
    return jsonify({"message": "Invalid token"}), 401

    @jwt.unauthorized_loader
    def unauthorized_callback(error):
    return jsonify({"message": "Missing or invalid Authorization header"}), 401

    if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)


    Auth Resource (auth_resource.py)

    from flask_restful import Resource, reqparse
    from flask_jwt_extended import (
    create_access_token, create_refresh_token, jwt_required, get_jwt_identity
    )
    from werkzeug.security import check_password_hash
    from datetime import timedelta
    from app.models import User

    parser = reqparse.RequestParser()
    parser.add_argument('username', required=True)
    parser.add_argument('password', required=True)

    class LoginResource(Resource):
    def post(self):
    data = parser.parse_args()
    user = User.query.filter_by(username=data['username']).first()

    if user and check_password_hash(user.password_hash, data['password']):
    access_token = create_access_token(identity=user.id, expires_delta=timedelta(seconds=30))
    refresh_token = create_refresh_token(identity=user.id, expires_delta=timedelta(minutes=2))
    return {'access_token': access_token, 'refresh_token': refresh_token}, 200
    return {'msg': 'Invalid credentials'}, 401

    class ProtectedResource(Resource):
    @jwt_required()
    def get(self):
    identity = get_jwt_identity()
    return {'logged_in_as': identity}, 200


    Testing Approach & Results (Postman) Login works (200 OK), returns tokens.

    Access protected resource (200 OK) initially with valid token.

    Wait 30 seconds (token expiration), then call protected resource again.

    Expected: 401 {"message": "Token has expired"}

    Actual: 500 Internal Server Error


    Server Logs:

    ERROR in app: Exception on /api/protected [GET]
    Traceback (most recent call last):
    File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
    File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
    File "/usr/local/lib/python3.11/site-packages/flask_restful/__init__.py", line 604, in dispatch_request
    resp = meth(*args, **kwargs)
    File "/usr/local/lib/python3.11/site-packages/flask_jwt_extended/view_decorators.py", line 167, in decorator
    verify_jwt_in_request(
    File "/usr/local/lib/python3.11/site-packages/flask_jwt_extended/utils.py", line 128, in decode_token
    return jwt_manager._decode_jwt_from_config(encoded_token, csrf_value, allow_expired)
    File "/usr/local/lib/python3.11/site-packages/jwt/api_jwt.py", line 363, in _validate_exp
    raise ExpiredSignatureError("Signature has expired")
    jwt.exceptions.ExpiredSignatureError: Signature has expired


    What I've Tried without success: Implemented JWT global error handlers (expired_token_loader).

    Verified correct registration of JWT callbacks.

    Simplified the endpoint code to remove internal exception handling (as JWT errors should be handled globally).

    Fully rebuilt Docker containers multiple times to ensure fresh deployment.


    Questions Why isn't my global expired_token_loader capturing the expired token exceptions from the decorator?

    Is there an error in the way I've configured flask_jwt_extended with flask_restful that prevents global handlers from triggering?

    What steps can I take to isolate or debug the issue further?


    Environment Details Python: 3.11 (Dockerized)

    Flask: latest

    Flask-JWT-Extended: latest

    Flask-Restful: latest

    PostgreSQL database (Dockerized)

    Docker Compose setup for backend, frontend, and database

    Continue reading...

Compartilhe esta Página