Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Errors (i.e. 401) not returned #86

Open
cloud-rocket opened this issue Oct 9, 2017 · 32 comments
Open

Errors (i.e. 401) not returned #86

cloud-rocket opened this issue Oct 9, 2017 · 32 comments

Comments

@cloud-rocket
Copy link

cloud-rocket commented Oct 9, 2017

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

@cloud-rocket
Copy link
Author

Now, I do see that default handlers are registered (in _set_error_handler_callbacks). But for some reason they never executed in my configuration

@cloud-rocket
Copy link
Author

I think it is related to the fact that I am using Restplus Api object with Flask Blueprint, so errorhandler should be used with api object and not with app as it is done in _set_error_handler_callbacks

@cloud-rocket
Copy link
Author

cloud-rocket commented Oct 9, 2017

Eventually, I recreated all the error handlers manually to solve it:

@api.errorhandler(jwt_extended_exception.NoAuthorizationError)
def handle_auth_error(e):
    return {'message': str(e)}, 401


@api.errorhandler(jwt_extended_exception.CSRFError)
def handle_auth_error(e):
    return {'message': str(e)}, 401


@api.errorhandler(jwt_exception.ExpiredSignatureError)
def handle_expired_error(e):
    return {'message': 'Token has expired'}, 401


@api.errorhandler(jwt_extended_exception.InvalidHeaderError)
def handle_invalid_header_error(e):
    return {'message': str(e)}, 422


@api.errorhandler(jwt_exception.InvalidTokenError)
def handle_invalid_token_error(e):
    return {'message': str(e)}, 422


@api.errorhandler(jwt_extended_exception.JWTDecodeError)
def handle_jwt_decode_error(e):
    return {'message': str(e)}, 422


@api.errorhandler(jwt_extended_exception.WrongTokenError)
def handle_wrong_token_error(e):
    return {'message': str(e)}, 422


@api.errorhandler(jwt_extended_exception.RevokedTokenError)
def handle_revoked_token_error(e):
    return {'message': 'Token has been revoked'}, 401


@api.errorhandler(jwt_extended_exception.FreshTokenRequired)
def handle_fresh_token_required(e):
    return {'message': 'Fresh token required'}, 401


@api.errorhandler(jwt_extended_exception.UserLoadError)
def handler_user_load_error(e):
    # The identity is already saved before this exception was raised,
    # otherwise a different exception would be raised, which is why we
    # can safely call get_jwt_identity() here
    identity = get_jwt_identity()
    return {'message': "Error loading the user {}".format(identity)}, 401


@api.errorhandler(jwt_extended_exception.UserClaimsVerificationError)
def handle_failed_user_claims_verification(e):
    return {'message': 'User claims verification failed'}, 400

But I think this case should be handled internally somehow

@vimalloc
Copy link
Owner

vimalloc commented Oct 9, 2017

It is an error with rest plus breaking native flask error handlers. Look at #83 for the details.

@vimalloc
Copy link
Owner

vimalloc commented Oct 10, 2017

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 :)

@cloud-rocket
Copy link
Author

cloud-rocket commented Oct 10, 2017 via email

@vimalloc
Copy link
Owner

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"
}

@abathur
Copy link
Contributor

abathur commented Oct 18, 2017

@vimalloc Just wanted to let you know that I ran into this as well. I also tried the solution you posted and didn't have any luck. I am guessing one of two things is going on:

  1. your stop-gap works fine with just the Api object, but doesn't work with an additional Blueprint object in the way (and some of the common getting-started examples for Restplus use the Blueprint).
  2. Some issues I've seen show different python 2/3 behavior here. I'm using 2.7, and I can see above that you're using 3.6. Not sure about @cloud-rocket.

I did some digging to figure out where this was going awry in Restplus and you can see the link above where I submitted it upstream. The upstream issue links to a gist, in case you'd like a small test-case to play with.

@vimalloc
Copy link
Owner

vimalloc commented Oct 18, 2017

Thanks for your work on this! Hopefully this will lead to changes in flask-restplus where native flask error handlers will just work. In the mean time, I tried to duplicate the example in your gist to check the error handler work around. I wasn't able to get the gist working by itself, it couldn't find the route for the restfplust api for some reason. Instead of spending time debugging that, I created a new example using (afaict) the same type of setup that you have in the gist.

from flask import Flask, Blueprint
from flask_jwt_extended import jwt_required, JWTManager, create_access_token
from flask_restplus import Api, Namespace, Resource

app = Flask(__name__)
app.config['SECRET_KEY'] = "test"
jwt = JWTManager(app)

api_v1 = Blueprint('api', __name__)
api = Api(api_v1, version='1.0', title='Todo API', description='A simple TODO API')

# This is the hack I added to get the error handlers to work with restplus
jwt._set_error_handler_callbacks(api)

ns = api.namespace('todo', description='TODO operations')

@ns.route('/access')
class ProductAccess(Resource):
    @jwt_required
    def post(self):
        return "", 200

    def get(self):
        return "", 200

@app.route("/token")
def token():
    return create_access_token(identity="test")

if __name__ == '__main__':
    app.register_blueprint(api_v1)
    print(app.url_map)
    app.run(debug=True)

Doing some testing on this, it looks like your guess about python2 and python3 was correct. This works correctly under python3 but not under python2:

http POST :5000/todos/access
HTTP/1.0 401 UNAUTHORIZED
Content-Length: 44
Content-Type: application/json
Date: Wed, 18 Oct 2017 18:16:04 GMT
Server: Werkzeug/0.12.2 Python/3.6.2

{
    "msg": "Missing Authorization Header"
}

http POST :5000/todos/access
HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Length: 43
Content-Type: application/json
Date: Wed, 18 Oct 2017 18:24:06 GMT
Server: Werkzeug/0.12.2 Python/2.7.14

{
    "message": "Internal Server Error"
}

I'm not sure why it is working in python3 and not python2 yet. Hopefully I can find some time to dig into this later this week. That said, I think the ideal situation would still be to see flask-restplus update their extension so it passes back errors to the flask error handlers.

Cheers!

EDIT: Here is the stacktrace for python2

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

@vimalloc
Copy link
Owner

vimalloc commented Oct 18, 2017

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.

@a410202049
Copy link

Flask-restplus and python2.7 is also used in my project. Is there a solution to this problem now?

@vimalloc
Copy link
Owner

I am not aware of any changes that have not been outlined in this ticket. If flask-restful is still bypassing the built in flask error handlers (make sure you have app.config['PROPOGATE_EXCEPTIONS'] = true), you may need to hack something onto flask-restful, or manually register the flask-jwt-extended error handlers to the flask-restful error system.

@sreecodeslayer
Copy link

sreecodeslayer commented Dec 6, 2018

I am not entirely sure what exactly the issue is but, i came across the very same issue on Python 3.6.7, today while testing a simple CRUD app that has Flask-Restful + SQLAlchemy + Flask-JWT-extended.

If i keep DEBUG as True, everything goes fine, but as soon I put the config mode to Production where my secret key, expiry and the DEBUG changes from Dev.

For eg.

class Development:
    DEBUG = True
    SECRET_KEY = "changeme"
    JWT_ACCESS_TOKEN_EXPIRES = False

    SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:postgres'\
        '@localhost/examples'

    # Suppress SQLALCHEMY_TRACK_MODIFICATIONS overhead warning
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class Production(Development):
    DEBUG = False
    SECRET_KEY = "sup3rs3cr3tk3yf0rPr0duct10N"
    SQLALCHEMY_DATABASE_URI = os.getenv(
        'SQLALCHEMY_DATABASE_URI',
        'postgresql://localhost/examples'
    )
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(7)

class Testing(Development):
    DEBUG = True
    TESTING = True
    SECRET_KEY = "testsecret"
    JWT_ACCESS_TOKEN_EXPIRES = False

    SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:postgres'\
        '@localhost/examples_test'

    # Suppress SQLALCHEMY_TRACK_MODIFICATIONS overhead warning
    SQLALCHEMY_TRACK_MODIFICATIONS = False

As you can see, I kept DEBUG as True for dev and test config, due to which I got no unit test breaks (I feel betrayed ☹️ ).
However, as the DEBUG is False in Production, it just returns 500 : Internal Server Error for flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header

Completely out of curiosity, I changed the Production.DEBUG to True and 500 is gone and as expected, there is a valid 401 : Missing Authorization Header.

Any idea why this happens, and where would the culprit to this cause be? 🤔

Here is a traceback of the 500:

Traceback (most recent call last):
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask_restful/__init__.py", line 480, in wrapper
    resp = resource(*args, **kwargs)
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask/views.py", line 88, in view
    return self.dispatch_request(*args, **kwargs)
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask_restful/__init__.py", line 595, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask_jwt_extended/view_decorators.py", line 102, in wrapper
    verify_jwt_in_request()
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask_jwt_extended/view_decorators.py", line 31, in verify_jwt_in_request
    jwt_data = _decode_jwt_from_request(request_type='access')
  File "/home/madboy/.virtualenvs/examples-rest/lib/python3.6/site-packages/flask_jwt_extended/view_decorators.py", line 284, in _decode_jwt_from_request
    raise NoAuthorizationError(errors[0])
flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header

@vimalloc
Copy link
Owner

vimalloc commented Dec 6, 2018

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.

@sreecodeslayer
Copy link

Wow, super duper quick reply 👏
Interesting nonetheless 😄
set app.config['PROPAGATE_EXCEPTIONS'] = True => Works 👍

@jperelli
Copy link

jperelli commented Jan 7, 2019

It works for me in python 3.6 with Blueprints

@svorcan
Copy link

svorcan commented Jan 24, 2019

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: flask-restful/flask-restful#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.

CleitonAlmeida added a commit to CleitonAlmeida/work-at-olist that referenced this issue Jan 31, 2019
Fix the Default callbacks are all provided, but never returned for flask_jwt

Solution: vimalloc/flask-jwt-extended#86
@vimalloc vimalloc pinned this issue Jun 19, 2019
@Jonyorker
Copy link

Jonyorker commented Jul 10, 2019

I'm not getting any luck with the app.config['PROPAGATE_EXCEPTIONS'] = True

What am I missing?

from flask_restplus import Resource, Api
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_jwt_extended import JWTManager
import base64
import os


app = Flask(__name__)

POSTGRES = {
    'user': os.environ.get('DATABASE_USER'),
    'pw': os.environ.get('DATABASE_PW'),
    'db': os.environ.get('DATABASE_NAME'),
    'host': os.environ.get('DATABASE_HOST'),
    'port': os.environ.get('DATABASE_PORT'),
}

app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = base64.b64decode(os.environ.get('TB_JWT_SECRET_KEY'))
app.config['JWT_ALGORITHM'] = 'HS512'
app.config['JWT_IDENTITY_CLAIM'] = 'sub'
app.config['PROPAGATE_EXCEPTIONS'] = True

app.url_map.strict_slashes = False

db = SQLAlchemy(app)
jwt = JWTManager(app)

from .main.v1 import blueprint as v1, url_prefix

migrate = Migrate(app, db)


@app.route('/')
def redirect_to_latest_api_version():
    """Redirects to /v1"""
    return redirect(url_prefix, code=302)


api = Api(app)

jwt._set_error_handler_callbacks(api)

app.register_blueprint(v1)```

@vimalloc
Copy link
Owner

@Jonyorker Flask restplus has a different issue that causes problems with native flask errorhandlers: noirbizarre/flask-restplus#340. You can work around that for the time being with the details here: #86 (comment)

@Jonyorker
Copy link

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.

@vimalloc
Copy link
Owner

Are you using python3? That work around doesn't work for python2 for whatever reason.

@Jonyorker
Copy link

3.7

@vimalloc
Copy link
Owner

Hrm, I'm not sure right off hand then. I'll try to take a closer when I have some downtime 👍

@Jonyorker
Copy link

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

Thanks!

@matusbielik
Copy link

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: flask-restful/flask-restful#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.

I agree that this is not the best solution although it works. Are there any plans to fix this or is PROPAGATE_EXCEPTIONS the official solution now?

@vimalloc
Copy link
Owner

@matusbielik As the PROPAGATE_EXCEPTIONS issues is part of flask-restful, there isn't anything I can do about it in this extension. I would suggest following up with that ticket in flask-restful to see if they are planning to resolve that.

@engmsaleh
Copy link

engmsaleh commented Aug 8, 2019

Setting app.config['PROPAGATE_EXCEPTIONS'] = True Didn't work for me :(

@vimalloc
Copy link
Owner

vimalloc commented Aug 8, 2019

Have you gone over the other solutions outlined in this ticket, for example if you are using flask-restplus? If none of them are working for you, please open a new ticket with all the details you can. Specifically, what version of flask and flask-jwt-extension you are using, what other flask extensions you are running, and a minimum example which demonstrates this behavior.

@engmsaleh
Copy link

engmsaleh commented Aug 8, 2019

I'm using flask-restplus and I have gone with all the suggested workarounds and neither of them worked for me

This is my main file

import os, sys
import config
import logging
import ast
import time
import datetime
from prometheus_client import (
    Summary, Histogram, generate_latest, CollectorRegistry)
from flask_jwt_simple import JWTManager
from flask_mongoengine import MongoEngine
from flask_restplus import Api
import logging.config
from flask_compress import Compress

db = MongoEngine()
jwt = JWTManager()
compress = Compress()

# application factory
def create_app(config):

    # create application instance
    app = Flask(__name__)
    #TODO: Check the problem with config
    app.config.from_object(config) 

    app.config['DEBUG'] = True

    ###### MongoDB ########
    app.config['MONGODB_SETTINGS'] = {
        'db': 'wallet',
        'host': 'mongodb://mongoadmin:mongopass@localhost:27017/wallet?authSource=admin'
    }
    db.init_app(app)
    
    # Setup the Flask-JWT-Extended extension
    #TODO: Move it to Config File 
    app.config['JWT_ALGORITHM'] = 'RS256'
    app.config['JWT_PUBLIC_KEY'] = open("./keys/public.key", "r").read()
    app.config['JWT_DECODE_AUDIENCE'] = 'ledger'
    app.config['JWT_IDENTITY_CLAIM'] = 'sub'
    app.config['PROPAGATE_EXCEPTIONS'] = True
    jwt.init_app(app)

    compress.init_app(app)
    
    from .apis import blueprint as api
    app.register_blueprint(api, url_prefix='/api/v1')
    api = Api(app=app, doc='/api/v1')

    # This is the hack I added to get the error handlers to work with restplus
    jwt._set_error_handler_callbacks(api)

    # Pass Flask logger to Gunicorn logger
    # gunicorn_logger = logging.getLogger('gunicorn.error')
    # app.logger.handlers = gunicorn_logger.handlers
    # app.logger.setLevel(gunicorn_logger.level)
    
    # app.logger.addHandler(logging.StreamHandler(sys.stdout))
    # app.logger.setLevel(logging.DEBUG)
    
    logging.config.fileConfig('./conf/logging.conf')
    logger = logging.getLogger(__name__)
    
    register_jwt_callbacks()
    
    return app

def register_jwt_callbacks():
    """Set up custom behavior for JWT based authentication.
    Return
    ------
        None
    """
    # @jwt.user_loader_callback_loader
    # def user_loader_callback(identity):
    #     """This function is going to be called everytime a user tries
    #     to access a protected endpoint.
    #     Args
    #     ----
    #         identity (User.username): This is the exact identity passed
    #                                   to `create_access_token` in auth.py
    #     Return
    #     ------
    #         User instance
    #     """
    #     return User.find_by_identity(identity)

    # TODO: should this method be taking in `self` as arg?
    @jwt.unauthorized_loader
    def jwt_unauthorized_callback(self):
        """We are just overriding `@jwt.unauthorized_loader`
        to return a custom error message
        """
        response = {
            'error': {
                'message': 'Your auth token or CSRF token are missing'
            }
        }
        # HTTP STATUS CODE 401 stands for Unauthorized Access
        return jsonify(response), 401

    @jwt.expired_token_loader
    def jwt_expired_token_callback():
        """We are just overriding `@jwt.expired_token_loader`
        to return a custom error message
        """
        response = {
            'error': {
                'message': 'Your auth token has expired'
            }
        }

        return jsonify(response), 401

    return None

@vimalloc
Copy link
Owner

vimalloc commented Aug 8, 2019

Are you using python3? The work around linked above with jwt._set_error_handler_callbacks does not work with python2. If that is not the issue please create a new issue that has a minimal example that I can run to troubleshoot further.

@engmsaleh
Copy link

Yes, I'm using python 3.7

Repository owner locked and limited conversation to collaborators Aug 8, 2019
@vimalloc
Copy link
Owner

vimalloc commented Aug 8, 2019

I'm going to lock this issue for now, as adding more comments on here only makes it harder to find the helpful solutions linked in this ticket.

As a summary, this issue is not caused by flask-jwt-extended, but rather by other flask extensions not working correctly with flask errorhandlers, a native feature in flask. Because of this, there is not a lot I can do on my end to fix their issues, I can only help try to find workarounds.

If you are running into this error, I would start by looking at these comments:

#86 (comment) : Flask-RESTful
#141 (comment): Flask-RESTful
#86 (comment) : Flask-RESTPlus

If you are still having issues, please open a new issue with a minimal complete and verifiable example to help me dig into this further. I will make sure to link any updates back here so the information is easily available.

Thanks! 👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants