flask-jwt-extended icon indicating copy to clipboard operation
flask-jwt-extended copied to clipboard

Errors (i.e. 401) not returned

Open cloud-rocket opened this issue 6 years ago • 32 comments

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 avatar Oct 09 '17 10:10 cloud-rocket

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 avatar Oct 09 '17 10:10 cloud-rocket

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 avatar Oct 09 '17 10:10 cloud-rocket

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

cloud-rocket avatar Oct 09 '17 11:10 cloud-rocket

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

vimalloc avatar Oct 09 '17 14:10 vimalloc

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

vimalloc avatar Oct 10 '17 15:10 vimalloc

I tried it - its not working

On Oct 10, 2017 6:22 PM, "Landon" [email protected] wrote:

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 Flaskfrom flask_jwt_extended import JWTManagerfrom 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 :)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/vimalloc/flask-jwt-extended/issues/86#issuecomment-335509456, or mute the thread https://github.com/notifications/unsubscribe-auth/ACU-vDf7fm8v8FHICqwVt4bpRisEbjIrks5sq4uzgaJpZM4PyP7J .

cloud-rocket avatar Oct 10 '17 15:10 cloud-rocket

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

vimalloc avatar Oct 10 '17 16:10 vimalloc

@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.

abathur avatar Oct 18 '17 16:10 abathur

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 avatar Oct 18 '17 18:10 vimalloc

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.

vimalloc avatar Oct 18 '17 19:10 vimalloc

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

a410202049 avatar Sep 21 '18 01:09 a410202049

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.

vimalloc avatar Sep 23 '18 01:09 vimalloc

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 :frowning_face: ). 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? :thinking:

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

sreecodeslayer avatar Dec 06 '18 18:12 sreecodeslayer

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.

vimalloc avatar Dec 06 '18 18:12 vimalloc

Wow, super duper quick reply :clap: Interesting nonetheless :smile: set app.config['PROPAGATE_EXCEPTIONS'] = True => Works :+1:

sreecodeslayer avatar Dec 06 '18 18:12 sreecodeslayer

It works for me in python 3.6 with Blueprints

jperelli avatar Jan 07 '19 20:01 jperelli

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.

svorcan avatar Jan 24 '19 10:01 svorcan

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

Jonyorker avatar Jul 10 '19 19:07 Jonyorker

@Jonyorker Flask restplus has a different issue that causes problems with native flask errorhandlers: https://github.com/noirbizarre/flask-restplus/issues/340. You can work around that for the time being with the details here: https://github.com/vimalloc/flask-jwt-extended/issues/86#issuecomment-335509456

vimalloc avatar Jul 10 '19 20:07 vimalloc

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.

Jonyorker avatar Jul 10 '19 20:07 Jonyorker

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

vimalloc avatar Jul 10 '19 20:07 vimalloc

3.7

Jonyorker avatar Jul 10 '19 20:07 Jonyorker

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

vimalloc avatar Jul 10 '19 20:07 vimalloc

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

Thanks!

Jonyorker avatar Jul 10 '19 20:07 Jonyorker

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?

matusbielik avatar Jul 25 '19 16:07 matusbielik

@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.

vimalloc avatar Jul 25 '19 16:07 vimalloc

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

engmsaleh avatar Aug 08 '19 02:08 engmsaleh

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.

vimalloc avatar Aug 08 '19 02:08 vimalloc

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

engmsaleh avatar Aug 08 '19 02:08 engmsaleh

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.

vimalloc avatar Aug 08 '19 03:08 vimalloc