Tags: #auth #authentication #basicauth #python #tornado #POST
"""
Explanation:
tornado.web.RequestHandler calls an internal `_execute` method before
calling any other method such as `get` or `post` etc.
we wrap the internal `_execute` method so that it either returns False if
no basic authentication provided or it will return the result of calling
the actual `_execute` method.
Tutorial:
http://kevinsayscode.tumblr.com/post/7362319243/easy-basic-http-authentication-with-tornado
"""
import base64
import tornado.ioloop
import tornado.web
def require_basic_auth(handler_class):
def wrap_execute(handler_execute):
# returns True is basic auth provided, otherwise it sends back a 401
# status code and requests user input their credentials.
#
# todo: write logic or pass in a function so that it can determine
# whether the authentication is accepted (e.g. you find the credentials
# within an external database).
def check_basic_auth(handler, kwargs):
def render_inputs(handler):
# If the browser didn't send us authorization headers,
# send back a response letting it know that we'd like
# a username and password (the "Basic" authentication
# method). Without this, even if your visitor puts a
# username and password in the URL, the browser won't
# send it. The "realm" option in the header is the
# name that appears in the dialog that pops up in your
# browser.
handler.set_status(401)
handler.set_header('WWW-Authenticate', 'Basic realm=Restricted') # noqa
handler._transforms = []
handler.finish()
auth_header = handler.request.headers.get('Authorization')
if auth_header is None or not auth_header.startswith('Basic '):
render_inputs(handler)
return False
# The information that the browser sends us is
# base64-encoded, and in the format "username:password".
# Keep in mind that either username or password could
# still be unset, and that you should check to make sure
# they reflect valid credentials!
auth_decoded = base64.decodebytes(bytes(auth_header[6:], 'utf-8'))
username, password = auth_decoded.decode('utf-8').split(':', 2)
kwargs['basicauth_user'], kwargs['basicauth_pass'] = username, password # noqa
# foo and bar should be pulled from somewhere, not hardcoded!
if username != "foo" and password != "bar":
render_inputs(handler)
return False
return True
# Since we're going to attach this to a RequestHandler class,
# the first argument will wind up being a reference to an
# instance of that class.
def _execute(self, transforms, *args, **kwargs):
if not check_basic_auth(self, kwargs):
return False
return handler_execute(self, transforms, *args, **kwargs)
# return our new function, which either returns False if basic auth
# wasn't provided, otherwise it returns the result of calling the
# original _execute function.
return _execute
# wrap tornado's internal `_execute` method, which is called first before
# any other method in the RequestHandler class
handler_class._execute = wrap_execute(handler_class._execute)
# return the modified class
return handler_class
@require_basic_auth
class MainHandler(tornado.web.RequestHandler):
# use !s inside format syntax to control string conversion
# https://docs.python.org/3.6/library/string.html#formatstrings
def get(self, basicauth_user, basicauth_pass):
msg = 'Hi there, {0!s}! Your password is {1}.'
body = msg.format(basicauth_user, basicauth_pass)
self.write(body)
def post(self, **kwargs):
basicauth_user = kwargs['basicauth_user']
basicauth_pass = kwargs['basicauth_pass']
msg = 'Hi there, {0!s}! Your password is {1}.'
body = msg.format(basicauth_user, basicauth_pass)
self.write(body)
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(9000)
tornado.ioloop.IOLoop.current().start()