pepoluan
pepoluan

Reputation: 6780

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

Upvotes: 0

Views: 360

Answers (1)

xyres
xyres

Reputation: 21734

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

Upvotes: 2

Related Questions