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:
- Add the Persona JavaScript library. (The idea is that this will eventually
be native to the browser with the library as a polyfill.) - Add the control for logging-in or out.
- Add login and logout handlers to your JavaScript.
- 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?