Search code examples
pythontornado

Tornado, how to drop POST/GET with 401 if a token is not provided


I want to drop GET/POST requests if a token field is not provided in the headers.

Currently I use this code (from GitHub Gist):

def require_basic_auth(handler_class):
    def wrap_execute(handler_execute):
        def serve_error(handler, status):
            handler._transforms = []  # necessary
            handler.set_status(status)
            handler.finish()

        def _execute(self, transforms, *args, **kwargs):
            expected_header = self.request.headers.get('X-User-Auth')

            if expected_header is None:
                return serve_error(self, 403)

            kwargs['token'] = expected_header
            # Token validation is done in the `post(self, token)` method

            return handler_execute(self, transforms, *args, **kwargs)

        return _execute

    handler_class._execute = wrap_execute(handler_class._execute)

    return handler_class

Two problems:

  1. It patches a _method, and I'm a bit uncomfortable manipulating methods whose names begin with a _
  2. It causes Uncaught Exception error: AttributeError: '_NullFuture' object has no attribute 'add_done_callback'

Reading a bit, I see that the .prepare() method might be the best way to implement this. But I haven't found any examples on how to do this properly in .prepare().

Can someone show me an example on how to do this?


Edit 1

I forgot to add: The decorator above also conveniently extracted the header into a token kwarg to be consumed by the post(self, token) method. I'm not sure how to do that if using prepare(self).


Solution

  • Yeah, directly patching Tornado's RequestHandler is not ideal.

    With Tornado, you're supposed to create a base class for your handlers. This base class acts like a "middleware" as seen in other frameworks (Django, Flask, etc.)

    Another pattern with Tornado is to create "mixin" classes which is useful for plugging in common features to specific handlers.

    This is what I do in my projects:

    class BaseHandler(web.RequestHandler):
        def prepare(self):
            expected_header = self.request.headers.get('X-User-Auth')
            
            if not expected_header:
                return self.send_error(401)
    
            # ... other common logic to run before other methods ...
    
    
    # Inherit your handlers from BaseHandler everywhere
    class MyHandler(BaseHandler):
        def get(self):
            pass