blog

Image of Bergman Persona from Wikimedia

Implementing Mozilla Persona

by

Mozilla Persona is decentralized sign-in protocol. (Actually, the protocol is named BrowserID, but Mozilla rebranded their implementation to confuse everyone. It worked.) It uses e-mail-like identifiers and public-key authentication to validate identity assertions. I’ll write more about how it works in another post (or if you’re impatient, you can read about it or watch a video about it.) Today, I’m going to implement it using Flask.

Getting Started

Since Persona is a solution for authentication, I’m going to start with an applicaton that shows you a list of people who recently visited a site. (It’s a simple Flask application that I wrote to learn about Flask.) The application is available in a public git repository. Note that I’m using the develop branch.

The steps to add Persona to an existing application are simple:

  1. Add the Persona JavaScript library. (The idea is that this will eventually
    be native to the browser with the library as a polyfill.)
  2. Add the control for logging-in or out.
  3. Add login and logout handlers to your JavaScript.
  4. Add assertion verification to your server application.

Adding the JavaScript Library

This is simple. Add the following line to your HTML:

<script src="https://login.persona.org/include.js"></script>

You could, in theory, host include.js yourself. Mozilla strongly suggests that you not do this, as Persona is “still in development”.

Adding the Authentication Control

My initial application has a form which is used for identification. I’m going to replace the form with a Persona button. The CSS-based buttons seem like a trendy way to do this, so I’ll use those. Here’s what I had before:

<form>
    <fieldset>
        <legend>Who are you?</legend>
        <label>
            <span>Identifier</span>
            <input
                id="identifier_input"
                placeholder="Anonymous"
                type="text"
                value="{{ me.identify() }}">
                </input>
        </label>
        <button id="action_button">Sign {{ me.is_anonymous() and 'in' or 'out' }}></button>
    </fieldset>
</form>

I’m going to replace that with a link:

<a href="#" class="persona-button"><span>Sign in with your e-mail</span></a>

Then I’m going to add the CSS file for the buttons. This goes in the <head> section:

<link rel="stylesheet" href="static/persona-buttons.css"/>

Adding Login and Logout handlers

After you’ve added the control, you’ll want it to do something. (You can see the identification code that I had before Persona.) Let’s adapt that to do something useful.

I’ve removed the click throttling, since authentication opens a new window.

Since I removed the login form, I’ve removed the logic associated with it. The event handlers for signing in and signing out are changed only because I’ve changed the DOM. The updateIdentifier function is changed to handle application/json instead of text/plain. Finally, I added a call to navigator.id.watch. This method is the primary browser interface for Persona.

The navigator.id.watch method requires two named parameters (in the form of passing an Object as the only argument). The first, onlogin is a function that is called when the user logs in. onlogin should pass the identification assertion to the server for verification. The second, onlogout is a function that is called when the user logs out. You should write them with the understanding that one or the other will be called when the user visits a new page. You can read the gory details in the MDN documentation.

Instead of pasting the whole file here, you can view view the changes at Bitbucket.

Trust, but Verify

All this means nothing if we cannot verify the assertion provided by the client. Persona provides a simple API for verification. I added the necessary imports, switched the serialization format to JSON and added a POST to the verification API:

@app.route('/signin', methods=['POST'])
def signin():
    global visitors
    me = visitors.get_me()
    me.refresh()
    payload = flask.request.stream.read(2048)
    data = json.loads(payload)
    if data.keys() != ['assertion']:
        return flask.jsonify(
                message='POST payload contained unexpected keys.',
                status='error')
    data['audience'] = 'localhost:5000'
    response = requests.post('https://verifier.login.persona.org/verify',
            data=data, verify=True)
    if response.ok:
        verificationdata = json.loads(response.content)
        me.identify(verificationdata['email'])
        return flask.jsonify(
                identifier=verificationdata['email'],
                status='success')
    else:
        return flask.jsonify(
                message='Received invalid response from Persona verifier.',
                status='error')
    flask.abort(500)
@app.route('/signout', methods=['POST'])
def signout():
    global visitors
    me = visitors.get_me()
    me.deidentify()
    return flask.jsonify(status='success')

If you prefer not to rely on Mozilla’s API, you can do the verification yourself. (Have fun with that.)

The Ugly

Persona is cool, but it is not without issues. While preparing for this post, I spent too long banging my head against an issue with third-party cookies. Persona is using third-party cookies to do session management. Without them, Mozilla insists that it works, but I haven’t found the magic incantations yet.

Note that Mozilla didn’t encounter the issue. If you figure it out, drop me a line in the comments.

Fin

In short, it’s nice to have another option in the authentication economy. Persona is not hard to implement and is worth your consideration. The last thing we need is 500 more websites getting e-mail verification and password handling wrong. The third-party cookie issue and Persona’s reliance on JavaScript are the only warts I’ve found. How about you? Will you be implementing Mozilla Persona?

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more