Persona Authentication: Say Goodbye to Passwords

Persona is Mozilla's attempt to fulfill an age old dream of an internet where we don't need a separate username/password pair for each new app or site we want to sign up for.

There's been plenty of takes on solving this, but Persona is probably the best shot anybody has taken at it so far, striking a great balance between ideals and being practical.

Your Email is Your Identity

One of the pragmatic decisions behind Persona is to use an email address as the base of a user's identity. This is different from OpenID or Facebook Connect, where any app or site has to request separate permission to get the users email address. Running a service with an authentication system where you don't need a good way of contacting your users is the exception rather than the rule, so using emails as IDs tends to simplify things greatly in practice.

See the Persona Login in Action

The long-term plan for Persona is to make it a built-in browser API for managing identification. Right now no browser, not even Firefox, implements Persona. But this is not a problem when it comes to using Persona today. Mozilla provides a JavaScript polyfil that'll let Persona work in any browser.

Here's a super-simple example of a site using Persona for identification: http://persona.yourwebisonline.com

If you try it out you'll see how simple this is for an end user. You click the "Sign In" button, get a dialog asking you to sign in with your email. If you've never used Persona before, you'll be asked to choose a password and go through a simple email verification process. In the end the pop-up closes and you're signed in. Once you have a Persona account, each new sign in (or sign up) requires just two clicks.

Implementing Persona Authentication

So what do you need to do to implement authentication with Persona? The good news is: not a lot!

The Persona JavaScript API is really well thought out and very simple. The server-side portion of working with API is as easy as doing a single HTTP request.

The JavaScript API is namespaced to navigator.id with the idea that someday it will simply be a part of modern browsers. Meanwhile you need to include an external javascript to use it: https://login.persona.org/include.js

Once this script is loaded the API exposes 3 methods:

navigator.id.request

Request the ID of the current user. Will pop-up the Persona dialog

navigator.id.logout

Logout the current user

navigator.id.watch

Watch any changes to the identity. Takes a loggedInUser that should be the user we currently register as being logged in on null. Also takes two callback functions: onLogin and onLogout.

The server needs to implement two actions: signin and signout. The first gets an assertion and need to verify it with the persona server. The second simply needs to clear the session.

Let's look at the most straight forward client-side Persona script:

// Make login buttons log in on click

$(".login-button").click(function(e) {

e.preventDefault();



// Request an ID - this will pop-up the Persona dialog

navigator.id.request();

});



// Make logout buttons log out on click

$(".logout-button").click(function(e) {

e.preventDefault();



// Trigger a logout

navigator.id.logout();

});



navigator.id && navigator.id.watch({

// We depend on the server to have set a global personaUser

// variable if a user is logged in

loggedInUser: personaUser || null,



// This method will be triggered once a user logs in

onlogin: function(assertion) {



// We need to send the assertion to the server

// so it can verify it and store the session

$.ajax({

url: "/persona/signin",

type: "post",

data: {assertion: assertion},

success: function() { document.location.reload() },

error: function() { navigator.id.logout() }

});

},



// This is triggered on a logout

onlogout: function() {



// Make sure the server gets a chance to reset

// the session and then refresh the page

$.post("/persona/signout", function() {

document.location.reload();

});

}

})

There's really not a lot to this: we bind some click handlers and then listen to the onLogin and onLogout events. The only little gotcha is the loggedInUser property. It's really important to set this to either the email of the currently logged in user, or null. Otherwise the Persona JS can get your site stuck in an endless redirect loop.

On The Server

The server barely has to do anything to make this work. Here's a dead simple example of using a Webpop extension to implement Persona:

// Require the HTTP module

var http = require("http");



// The persona endpoint

var ENDPOINT = "https://verifier.login.persona.org/verify";



// If the extension is called persona.js, these routes will respond to

// POST requests to /persona/signin and /persona/signout

exports.routes = {

post: {

"signin": function(params) {

// Do a POST requset to the ENDPOINT

var data = http.post(ENDPOINT, {

data: {

// The assertion we got from the client-side AJAX request

assertion: params.assertion,

// The URL of our site

audience: site.url

}

});



// Set the persona_user in the session

request.session.persona_user = data.email;



// Send back a response to the client

response.send(JSON.stringify(data), {'Content-Type': 'application/json'});

},



"signout": function(params) {

// Delete the persona_user from the session

request.session.persona_user = null;



// Send back a response to the client

response.send("{}", {'Content-Type': 'application/json'});

}

}

}

Not much to this either — Persona really is that simple.

When we get a POST request to the signin URL we just ask https://verifier.login.persona.org if the assertion we got is valid for our site's URL. If it is, we store the user email in our session. A real life app would normally also persist the user in a database and assign permissions, etc.

When we get a POST request to the signout URL we just clean up the session, and that's it.

Personalizing the dialog

We can make the user experience even better by supplying a few options to navigator.id.request method.

siteName will make the Persona dialog show the name of our site above the URL.

If our site is running under HTTPS we can also supply a siteLogo to be shown above the siteName.

HTTPS sites can also set a path for termsOfService and privacyPolicy. When these are present (you must specify both or none) the user will be asked to agree to the Terms of Service and the Privacy Policy of the site as part of the sign in.

Here's how an ID request would look with all of those present:

navigator.id.request({ siteName: "Persona Example", siteLogo: "/images/logo.png", termsOfService: "/terms-of-service", privacyPolicy: "/privacy-policy" });

That's pretty much all there is to implementing a Persona based authentication system. For more info, check out the official docs. We've also prepared a minimal Persona Webpop theme with all you need to get started. Grab it here!