Integrate BrowserID in a Tornado web app

22 November 2011   2 comments   Tornado, Mozilla

Powered by Fusion×

Integrate BrowserID in a Tornado web app BrowserID is a new single sign-on initiative lead by Mozilla that takes a very refreshing approach to single sign-on. It's basically like OpenID except better and similar to the OAuth solutions from Google, Twitter, Facebook, etc but without being tied to those closed third-parties.

At the moment, BrowserID is ready for production (I have it on Kwissle) but the getting started docs is still something that is under active development (I'm actually contributing to this).

Anyway, I thought I'd share how to integrate it with Tornado

First, you need to do the client-side of things. I use jQuery but that's not a requirement to be able to use BrowserID. Also, there are different "patterns" to do login. Either you have a header that either says "Sign in"/"Hi Your Username". Or you can have a dedicated page (e.g. Let's, for simplicity sake, pretend we build a dedicated page to log in. First, add the necessary HTML:

<a href="#" id="browserid" title="Sign-in with BrowserID">
  <img src="/images/sign_in_blue.png" alt="Sign in">
<script src="" async></script>

Next you need the Javascript in place so that clicking on the link above will open the BrowserID pop-up:

function loggedIn(response) {
  location.href = response.next_url;
  /* alternatively you could do something like this instead:
       $('#header .loggedin').show().text('Hi ' + response.first_name);
    ...or something like that */

function gotVerifiedEmail(assertion) {
 // got an assertion, now send it up to the server for verification
 if (assertion !== null) {
     type: 'POST',
     url: '/auth/login/browserid/',
     data: { assertion: assertion },
     success: function(res, status, xhr) {
       if (res === null) {}//loggedOut();
       else loggedIn(res);
     error: function(res, status, xhr) {
       alert("login failure" + res);
 else {

$(function() {
  $('#browserid').click(function() {;
    return false;

Next up is the server-side part of BrowserID. Your job is to take the assertion that is given to you by the AJAX POST and trade that with for an email address:

import urllib
import tornado.web
import tornado.escape 
import tornado.httpclient

@route('/auth/login/browserid/')  # ...or whatever you use
class BrowserIDAuthLoginHandler(tornado.web.RequestHandler):

   def check_xsrf_cookie(self):  # more about this later

   def post(self):
       assertion = self.get_argument('assertion')
       http_client = tornado.httpclient.AsyncHTTPClient()
       domain = ''  # MAKE SURE YOU CHANGE THIS
       url = ''
       data = {
         'assertion': assertion,
         'audience': domain,
       response = http_client.fetch(

   def _on_response(self, response):
       struct = tornado.escape.json_decode(response.body)
       if struct['status'] != 'okay':
           raise tornado.web.HTTPError(400, "Failed assertion test")
       email = struct['email']
       self.set_secure_cookie('user', email,
       self.set_header("Content-Type", "application/json; charset=UTF-8")
       response = {'next_url': '/'}

Now that should get you up and running. There's of couse a tonne of things that can be improved. Number one thing to improve is to use XSRF on the AJAX POST. The simplest way to do that would be to somehow dump the XSRF token generated into your page and include it in the AJAX POST. Perhaps something like this:

var _xsrf = '{{ xsrf_token }}';
function gotVerifiedEmail(assertion) {
 // got an assertion, now send it up to the server for verification
 if (assertion !== null) {  
     type: 'POST',
     url: '/auth/login/browserid/',
     data: { assertion: assertion, _xsrf: _xsrf },

Another thing that could obviously do with a re-write is the way users are handled server-side. In the example above I just set the asserted user's email address in a secure cookie. More realistically, you'll have a database of users who you match by email address but instead store their database ID in a cookie or something like that.

What's so neat about solutions such as OpenID, BrowserID, etc. is that you can combine two things in one process: Sign-in and Registration. In your app, all you need to do is a simple if statement in the code like this:

user = self.db.User.find_by_email(email) 
if not user:
    user = self.db.User() = email
self.set_secure_cookie('user', str(

Hopefully that'll encourage a couple of more Tornadonauts to give BrowserID a try.


Sam Penrose
Hey, this is great stuff! Thanks for being an early supporter. We have since changed a couple of domains, so: => =>

Also, getVerifiedEmail() is deprecated. Finally, I am no Tornado hacker, but would the "validate_cert" kwarg be available in when you call http_client.fetch()? If so, using it would be a Good Thing.

Thanks again for a great blog post!
-- Sam
Peter Bengtsson
Thank you so much!
The site that I implemented it on I no longer support but your comment will hopefully help in guiding poor people who end up here.

Your email will never ever be published

Related posts

Trivial but powerful tips for nosetests 19 November 2011
Python file with closing automatically 03 December 2011
Related by keywords:
Future of Web Apps (quick summary and thoughts) 04 October 2007
Common names amongst my Facebook friends 26 June 2014
Registration and sign-in by email verification 29 April 2013
Git + Twitter = Friedcode 22 April 2009
OpenID, Attribute Exchange, SReg, python-openid and Google 23 April 2010 - launched and ready! 06 April 2011
Beach volleyball bums 02 August 2012
Too Cool For Me? 25 September 2011
Bookmarklet to replace the current domain with localhost:8000 17 January 2010
MathML and displaying Math on the web 23 January 2004
My first Twitter app - 22 September 2009
iPhone push notifications for Twitter with Prowl 25 October 2009