最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - JWT Token Expiration Handling Causing 500 Error in Flask-JWT-Extended and Flask-Restful - Stack Overflow

programmeradmin4浏览0评论

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 401 Unauthorized response.

Desired Behavior: When a JWT token expires, my API should 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

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 401 Unauthorized response.

Desired Behavior: When a JWT token expires, my API should 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

Share Improve this question edited Apr 2 at 3:23 Charles Duffy 297k43 gold badges434 silver badges489 bronze badges asked Apr 1 at 12:08 24wolF24wolF 213 bronze badges New contributor 24wolF is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 2
  • Thanks a lot. Sorry I didn’t have my error logs tabbed out. – 24wolF Commented Apr 1 at 13:04
  • To mark your question as solved, use the "add an answer" button to add an answer, separate from the text of the question itself, then (later, when the system allows it) click the checkbox by that answer to accept it. Do not edit answers into questions. – Charles Duffy Commented Apr 2 at 3:23
Add a comment  | 

1 Answer 1

Reset to default 0

Figured out that I had to force handling JWT exceptions globally:

Configured Flask and Flask-RESTful to propagate JWT exceptions correctly by adding the following code to init.py:

app.config['PROPAGATE_EXCEPTIONS'] = True  # Propagate exceptions to the client
api.handle_errors = False                  # Disable Flask-RESTful 

This provided the results I was looking for and I successfully tested the JWT lifecycle:

  1. Login: Issued JWT tokens via /api/login.

  2. Valid Token: Accessed protected resource successfully.

  3. Expired Token: Received expected 401 error ("Token has expired").

  4. Token Refresh: Successfully refreshed JWT token via /api/refresh.

  5. New Token: Validated new token with protected endpoint access.

    Sources for the developed solution:

    1. https://github/vimalloc/flask-jwt-extended/issues/308

    2. https://github/vimalloc/flask-jwt-extended/issues/86

    3. https://github/vimalloc/flask-jwt-extended/issues/83

    4. https://github/vimalloc/flask-jwt-extended/blob/main/flask_jwt_extended/jwt_manager.py#L81

发布评论

评论列表(0)

  1. 暂无评论