Search code examples
pythonflaskmiddlewareabort

Flask middleware on abort 401 causing a 500


I have a middleware in my Flask app that is used to authenticate a JSON Web Token coming in the header of the request and is checking to verify it, below is my middleware class:

class AuthMiddleware(object):

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        path = environ.get('PATH_INFO')
        if path != '/authenticate' or path != '/token':
            token = environ.get('HTTP_X_ACCESS_TOKEN')
            verfied_token = verify_token(token)
            if verfied_token is False:
                abort(401)
            elif verfied_token is True:
                # payload = get_token_payload(token)
                # check_permissions(payload)
                pass

        return self.app(environ, start_response) 

verify_token() is a function that will return True or False, and if it returns False, I want it to abort with an error 401. However, it aborts with an error 500:

127.0.0.1 - - [25/Mar/2015 11:37:25] "POST /role HTTP/1.1" 500 -
Error on request:
Traceback (most recent call last):
File "/ENV/lib/python2.7/site-packages/werkzeug/serving.py", line 180, in run_wsgi
execute(self.server.app)
File "/ENV/lib/python2.7/site-packages/werkzeug/serving.py", line 168, in execute
application_iter = app(environ, start_response)
File "/ENV/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/middleware.py", line 24, in __call__
return self.app(environ, start_response) 
File "/ENV/lib/python2.7/site-packages/werkzeug/exceptions.py", line 605, in __call__
raise self.mapping[code](*args, **kwargs)
BadRequest: 400: Bad Request

In my views, I abort a 401 like it should, but here it seems to be a problem. What should I do?


Solution

  • The middleware you've shown runs some other code and then calls the wrapped Flask application. However, abort raises exceptions that Flask handles, but aren't handled by WSGI directly. Since you're not in the Flask application yet, it can't handle the exception.

    A much easier way would be to do this check inside the Flask app. Create a before_request handler that does basically the same thing as the middleware, except you can use flask.request rather than needing to parse the path and headers yourself.

    from flask import request, abort
    
    @app.before_request
    def check_auth_token():
        if request.path in ('/authenticate', '/token'):
            return
    
        token = request.headers.get('X-ACCESS-TOKEN')
    
        if not verify_token(token):
            abort(401)
    
        check_permissions(get_token_payload(token))
    

    If you do want to use WSGI middleware for this, you need to create the response yourself. Conveniently, Werkzeug's exceptions behave like WSGI applications, so it's straightforward to use them.

    from werkzeug.exceptions import Unauthorized
    
    # in place of abort(401) in the middleware
    return Unauthorized()(environ, start_response)
    

    You can also use abort still by catching the exceptions it raises (which, again, are WSGI applications).

    from werkzeug.exceptions import abort, HTTPException
    # werkzeug.exceptions.abort is the same as flask.abort
    
    try:
        abort(401)
    except HTTPException as e:
        return e(environ, start_response)