flask-jwt-extended: Errors (i.e. 401) not returned

It looks to me that this part is not yet implemented:

def jwt_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        jwt_data = _decode_jwt_from_request(request_type='access')
        ctx_stack.top.jwt = jwt_data
        _load_user(jwt_data[config.identity_claim])
        return fn(*args, **kwargs)
    return wrapper

The upper code raises many different exceptions, but I don’t see any code returning the errors (my own default error handling of restplus triggers 500 error every time).

The documentation states that:

If the access token is not valid for any reason (missing, expired, tampered with, etc) we will return json in the format of {‘msg’: ‘why accessing endpoint failed’} along with an appropriate http status code (generally 401 or 422).

Default callbacks are all provided, but never returned.

Am I wrong?

Thanks, Meir Tseitlin

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 1
  • Comments: 32 (15 by maintainers)

Commits related to this issue

Most upvoted comments

I’ve thought of a better way to solve this. It is very much a hack, and I still think flask-restplus should fix their extension so that it does not break native flask features, but it should get you up and going safer then how you have it handled above.

It looks like the errorhandler method for restplus uses the same signature that flask error handler does, so you could take advantage of duck typing and access this internal method to set the errors on the restplus level: https://github.com/vimalloc/flask-jwt-extended/blob/master/flask_jwt_extended/jwt_manager.py#L81

from flask import Flask
from flask_jwt_extended import JWTManager
from flask_restplus import Api

app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret'  # Change this!
jwt = JWTManager(app)
api = Api()

# This is where the duck typing magic comes in
jwt._set_error_handler_callbacks(api)

This would obviously be prone to break if I changed how the underlying part of this extension worked, as you are accessing a private method that doesn’t have any guarantees on it, but I do not see any reason why that method would change in the foreseeable future, and this would insure that any new or changed error handles in this extension would get properly set on the flask-restplus extension.

Hope this helps 😃

Yeah, for whatever reason when you are using flask-restful you will need to set app.config['PROPAGATE_EXCEPTIONS'] = True in order for the error handlers to properly work. I would expect that to fix your issue.

Yeah, for whatever reason when you are using flask-restful you will need to set app.config['PROPAGATE_EXCEPTIONS'] = True in order for the error handlers to properly work. I would expect that to fix your issue.

I had similar issue, and after a little bit of digging through code I raised issue in Flask-RESTful about this: https://github.com/flask-restful/flask-restful/issues/796

I am not sure that enabling PROPAGATE_EXCEPTIONS in production environment is the right way to go, since that way we would leave any actual exceptions without registered handlers completely unhandled.

It works for me if I try the following. Are you doing something different?:

from flask import Flask, jsonify, request
from flask_jwt_extended import (
    JWTManager, jwt_required, create_access_token,
    get_jwt_identity
)
from flask_restplus import Resource, Api

app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret'  # Change this!
jwt = JWTManager(app)
api = Api(app)
jwt._set_error_handler_callbacks(api)


@api.route('/hello')
class HelloWorld(Resource):
    @jwt_required
    def get(self):
        return {'hello': 'world'}


@api.route('/login')
class Login(Resource):
    def post(self):
        username = request.json.get('username', None)
        password = request.json.get('password', None)
        if username != 'test' or password != 'test':
            return jsonify({"msg": "Bad username or password"}), 401
        access_token = create_access_token(identity=username)
        return {'access_token': access_token}


if __name__ == '__main__':
    app.run(debug=True)

And using it:

$ http GET :5000/hello                             
HTTP/1.0 401 UNAUTHORIZED
Content-Length: 44
Content-Type: application/json
Date: Tue, 10 Oct 2017 16:06:28 GMT
Server: Werkzeug/0.12.2 Python/3.6.2

{
    "msg": "Missing Authorization Header"
}


$ http POST :5000/login username=test password=test
HTTP/1.0 200 OK
Content-Length: 302
Content-Type: application/json
Date: Tue, 10 Oct 2017 16:06:30 GMT
Server: Werkzeug/0.12.2 Python/3.6.2

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDc2NTI0OTAsImlhdCI6MTUwNzY1MTU5MCwibmJmIjoxNTA3NjUxNTkwLCJqdGkiOiI4YWRjYzQyOS02MmE0LTRlNTAtYjhhZS05MmU0MTA4YTUyZDMiLCJpZGVudGl0eSI6InRlc3QiLCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.ixGtDywN2SVyBHMeSLXZq8g0fs0VwgbIARUXP8CaITQ"
}


$ http GET :5000/hello Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDc2NTI0OTAsImlhdCI6MTUwNzY1MTU5MCwibmJmIjoxNTA3NjUxNTkwLCJqdGkiOiI4YWRjYzQyOS02MmE0LTRlNTAtYjhhZS05MmU0MTA4YTUyZDMiLCJpZGVudGl0eSI6InRlc3QiLCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.ixGtDywN2SVyBHMeSLXZq8g0fs0VwgbIARUXP8CaITQ"
HTTP/1.0 200 OK
Content-Length: 25
Content-Type: application/json
Date: Tue, 10 Oct 2017 16:06:49 GMT
Server: Werkzeug/0.12.2 Python/3.6.2

{
    "hello": "world"
}

I appreciate it. Up for whatever you need me to provide you to debug it.

Thanks!

RIght, but I’ve added in the jwt._set_error_handler_callbacks(api) and the progapge_exceptions and I’m still getting the error 500 instead of whatever 40* it should be.

The problem that I am seeing in python2 with my hack applied stems from here: https://github.com/noirbizarre/flask-restplus/blob/master/flask_restplus/api.py#L583

My error handlers return a flask response, not a python dictionary, which ends up in default_data and triggers a AttributeError: 'Response' object has no attribute 'get' here: https://github.com/noirbizarre/flask-restplus/blob/master/flask_restplus/api.py#L599

This in turn causes the original app error handler to be run here https://github.com/noirbizarre/flask-restplus/blob/master/flask_restplus/api.py#L567 and leads to the following stacktrace:

Traceback (most recent call last):
  File "/tmp/foo/venv/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/tmp/foo/venv/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/tmp/foo/venv/lib/python2.7/site-packages/flask_restplus/api.py", line 557, in error_router
    return original_handler(e)
  File "/tmp/foo/venv/lib/python2.7/site-packages/flask/app.py", line 1504, in handle_user_exception
    assert exc_value is e
AssertionError

Funny enough, in python3 the exact same sequence happens. By registering our error handlers on the flask-restplus api object, we aren’t actually having flask-restplus serve our error handlers. We are just setting up a situation where an exception is raised in the flask-restplus error handling, and that triggers flask-restplus to kick the error back up to the native flask error. I’m guessing the reason why this works with python3 and not python2 boils down to the subtle differences in how exceptions work between them.

Regardless, what I would really like to see is flask-restplus kicking the exception back up to the native flask error handlers, so that no magic needs to be done to use these extensions together.