The navigator.id API

On November 30th, 2016, Mozilla shut down the persona.org services. Persona.org and related domains will soon be taken offline.

For more information, see this guide to migrating your site away from Persona:

https://wiki.mozilla.org/Identity/Persona_Shutdown_Guidelines_for_Reliers

For full details about the navigator.id API, refer to its reference pages.

With Persona, a website asks the user to provide an "assertion", which is a digitally signed email address. By verifying the signature, the site can be assured that the user really does control the address in question. The site can then use this email address as an identifier for that user.

To ask for an assertion, the website uses a JavaScript API defined by the id object, which is a member of the global navigator object.

In future we expect the id object to be built into the browser, but at the moment it isn't, so sites using Persona need to include the polyfill library hosted at https://login.persona.org/include.js in their pages. After that, they can work as if id is just a built-in member of navigator.

There are two current versions of the API: the "Callback API", and the newer "Observer API".

The Callback API

The Callback API consists of a single function, get(). It takes two arguments:

  • a callback function that will be called back with a signed assertion if the user successfully authenticates to their Identity Provider
  • an options object that mostly customizes the dialog presented to users: allowing a website to link to its privacy policy, to set background color or include an icon, for example

You call get(), and in the callback you send the assertion to the server to verify the assertion. If verification succeeds, you can log the user in:

var signin = document.getElementById('sign-in');
signin.addEventListener("click", getAssertion, false);
// get an assertion
function getAssertion() {
  navigator.id.get(verifyAssertion, {
                     backgroundColor: "#606B72",
                     siteName: "My Example Site"
                   });
}
// send the assertion to the server for verification
function verifyAssertion(assertion) {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/verify", true);
  var param = "assertion="+assertion;
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.setRequestHeader("Content-length", param.length);
  xhr.setRequestHeader("Connection", "close");
  xhr.send(param);
  xhr.onload = handleVerificationResponse(xhr);
}

The Observer API

The Observer API consists of three functions: request(), watch(), and logout().

You call request() to ask for a signed assertion, as you do using get() in the old API. But request() doesn't take a callback parameter: instead, you call another function watch(), with which you register callbacks for onlogin and onlogout events. Persona calls these callbacks when a user logs in or out respectively. Like the callback to get(), the onlogin callback gets a signed assertion for you to verify.

You call logout() when a user logs out of your site, so Persona can update its state.

The main difference between this API and the Callback API is that the Callback API doesn't maintain any state: it doesn't have any idea which user is currently logged into Persona in general or any site in particular. Each website is responsible for its own session management.

By contrast the Observer API implements session management: the browser keeps track of which user it thinks is logged into a particular site and can trigger the onlogin and onlogout callbacks when its idea of the state changes. Because of this, you need to ensure that your website's state and Persona's state are kept in synch, and this tends to make the Observer API more complex to use.

Here's an example showing the Observer API:

var signin = document.getElementById('sign-in');
var signout = document.getElementById('sign-out');
signin.addEventListener("click", function() {
  navigator.id.request();
}, false);
signout.addEventListener("click", function() {
  navigator.id.logout();
}, false);
function handleUserResponse(xhr) {
  return function() {
    if (xhr.status == 200) {
        if (xhr.responseText == "no user") {
            logoutUser();
        }
        else {
            loginUser(xhr.responseText);
        }
      navigator.id.watch({
        loggedInUser: currentUser,
        onlogin: function(assertion) {
          verifyAssertion(assertion);
        },
        onlogout: function() {
          logoutUser();
        }
      });
    }
    else {
        navigator.id.logout();
        alert("XMLHttpRequest error: " + xhr.status);
    }
  }
}
function checkCurrentUser() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/currentuser", true);
  xhr.send();
  xhr.onload = handleUserResponse(xhr);
}
checkCurrentUser();
function loginUser(loggedInUser) {
  // update your UI to show that the user is logged in
}
function logoutUser() {
  // update your UI to show that the user is logged out
}
function handleVerificationResponse(xhr) {
  // if successfully verified, log the user in
}
function verifyAssertion(assertion) {
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/verify", true);
  var param = "assertion="+assertion;
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.setRequestHeader("Content-length", param.length);
  xhr.setRequestHeader("Connection", "close");
  xhr.send(param);
  xhr.onload = handleVerificationResponse(xhr);
}

This code adds listeners which call request() and logout() when the user clicks "Sign in" and "Sign out" respectively. It then checks whether the server thinks someone is logged in already, and updates its UI if it thinks someone is. The code then calls watch() to start listening for login and logout events. When Persona triggers onlogout the code updates its state to log the user out, and when Persona triggers onlogin the code sends the assertion to the server for verification.

Although the Observer API is more complex to use, it provides two extra features: seamless first-time sign-in, and global logout.

Seamless first-time sign-in

The first time a user signs in using Persona, they may need to create a Persona account. This will happen if they haven't used Persona before and if their email provider does not support Persona. In this case they will be invited to create an account using the fallback provider operated by Mozilla, which will ask them to prove ownership of their email address. After creating an account, if the original website uses the Callback API, the user will not be automatically redirected to the website and signed in: they will have to navigate back to it.

If the website uses the Observer API, then once the user finishes creating their account with the fallback provider, they will be immediately redirected to the site, the watch() API will call the onlogin listener, and the user will be signed in.

Global logout

With the Observer API, the website listens to the onlogout event. If the user signs out of Persona by visiting https://login.persona.org/ and signing out there, then the Observer API triggers this event for all websites that are listening. In response, the site should log the user out.

This means that if the user doesn't always trust the other people that may access their computer, they can sign out in one place, and this will be propagated to all sites that use the Observer API. 

Document Tags and Contributors

 Contributors to this page: Sheppy, wbamberg, HugoDaniel, Callahad
 Last updated by: Sheppy,