Basic authentication on Tornado with a decorator

February 12, 2013

Tornado is an open source web server developed by Facebook. It implement various third-party authentication schemes to connect to services like Facebook, Google OAuth, Twitter, etc. But Tornado doesn’t provide a good documentation when you try to handle your own login service. I tried to do mine.

My goal is to allow a user to access my web application when he has good permissions. I add 3 handlers, one for my index (MainHandler), one for my login page (AuthLoginHandler), one for my logout page (AuthLogoutHandler).

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
            (r"/auth/login/", AuthLoginHandler),
            (r"/auth/logout/", AuthLogoutHandler),
        ]
        settings = {
            "template_path":Settings.TEMPLATE_PATH,
            "static_path":Settings.STATIC_PATH,
            "debug":Settings.DEBUG,
            "cookie_secret": Settings.COOKIE_SECRET,
            "login_url": "/auth/login/"
        }
        tornado.web.Application.__init__(self, handlers, **settings)

The settings[“login_url”] property set the url to be used by the @authenticated decorator. What I want is to redirect the user to login url (/auth/login/) if he’s not identified.

class MainHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        username = tornado.escape.xhtml_escape(self.current_user)
        self.render("index.html", username = username)

It’s so simple, isn’t it ?

It remains for me to create a handler for my login screen, and a handler to delete my cookie when i reach auth/logout/ url.

class AuthLoginHandler(BaseHandler):
    def get(self):
        try:
            errormessage = self.get_argument("error")
        except:
            errormessage = ""
        self.render("login.html", errormessage = errormessage)

    ...

class AuthLogoutHandler(BaseHandler):
    def get(self):
        self.clear_cookie("user")
        self.redirect(self.get_argument("next", "/"))

My login handler get method render the login.html page.

<form action="/auth/login/" method="post" id="login_form">
    <fieldset>
        <label for="username">Username</label>
        <input class="text-input" id="username" name="username" type="text" value="">
    </fieldset>

    <fieldset>
        <label for="password">Password</label>
        <input class="text-input" id="password" name="password" type="password" value="">
    </fieldset>

    <fieldset>
        <span class="errormessage"></span>
    </fieldset>

    <div id="form_btn">
        <input id="signin-btn" class="btn btn-blue" type="submit" value="Sign In" tabindex="3">
    </div>
</form>

login form

When a user makes a POST request on /auth/login/, my web server validates if the pair username/password is good and writes the user cookie. Otherwise it redirects the user to the login page with an error message.

class AuthLoginHandler(BaseHandler):
    def get(self):
        try:
            errormessage = self.get_argument("error")
        except:
            errormessage = ""
        self.render("login.html", errormessage = errormessage)

    def check_permission(self, password, username):
        if username == "admin" and password == "admin":
            return True
        return False

    def post(self):
        username = self.get_argument("username", "")
        password = self.get_argument("password", "")
        auth = self.check_permission(password, username)
        if auth:
            self.set_current_user(username)
            self.redirect(self.get_argument("next", u"/"))
        else:
            error_msg = u"?error=" + tornado.escape.url_escape("Login incorrect")
            self.redirect(u"/auth/login/" + error_msg)

    def set_current_user(self, user):
        if user:
            self.set_secure_cookie("user", tornado.escape.json_encode(user))
        else:
            self.clear_cookie("user")

All the current user informations are saved in a secure cookie. Tornado provide set_secure_cookie and get_secure_cookie methods. These two methods use a cookie secret key to encrypt the cookie.

Hope this helped !

See the gist associate

By Guillaume Vincent