mozilla

Revision 478195 of The navigator.id API

  • Revision slug: Mozilla/Persona/The_navigator.id_API
  • Revision title: The navigator.id API
  • Revision id: 478195
  • Created:
  • Creator: wbamberg
  • Is current revision? No
  • Comment

Revision Content

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().

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. 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 called onlogin and onlogout. 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.

Because Persona maintains its own state with the Observer API, 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(assertion) {
  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. 

Revision Source

<div class="note">
  For full details about the <code>navigator.id</code> API, refer to its <a href="/en-US/docs/Web/API/navigator.id">reference pages</a>.</div>
<p>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.</p>
<p>To ask for an assertion, the website uses a JavaScript API defined by the <code>id</code> object, which is a member of the global <a href="/en-US/docs/Web/API/Navigator"><code>navigator</code></a> object.</p>
<p>In future we expect the <code>id</code> 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 <a class="external link-https" href="https://login.persona.org/include.js" title="https://login.persona.org/include.js">https://login.persona.org/include.js</a> in their pages. After that, they can work as if <code>id</code> is just a built-in member of <code>navigator</code>.</p>
<p>There are two current versions of the API: the "Callback API", and the newer "Observer API".</p>
<h2 id="The_Callback_API">The Callback API</h2>
<p>The <a href="/en-US/docs/Web/API/navigator.id#CallbackMethods">Callback API</a> consists of a single function, <a href="/en-US/docs/Web/API/navigator.id.get"><code>get()</code></a>. It takes two arguments:</p>
<ul>
  <li>a callback function that will be called back with a signed assertion if the user successfully authenticates to their Identity Provider</li>
  <li>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</li>
</ul>
<p>You call <code>get()</code>, and in the callback you send the assertion to the server to <a href="/en-US/docs/Mozilla/Persona/Remote_Verification_API">verify the assertion</a>. If verification succeeds, you can log the user in:</p>
<pre class="brush: js">
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);
}</pre>
<h2 id="The_Observer_API">The Observer API</h2>
<p>The <a href="/en-US/docs/Web/API/navigator.id#ObserverMethods">Observer API</a> consists of three functions: <a href="/en-US/docs/Web/API/navigator.id.request"><code>request()</code></a>, <a href="/en-US/docs/Web/API/navigator.id.request"><code>watch()</code></a>, and <a href="/en-US/docs/Web/API/navigator.id.request"><code>logout()</code></a>.</p>
<p>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.</p>
<p>By contrast the Observer API implements session management. You call <code>request()</code> to ask for a signed assertion, as you do using <code>get()</code> in the old API. But <code>request()</code> doesn't take a callback parameter: instead, you call another function <code>watch()</code>, with which you register callbacks called <code>onlogin</code> and <code>onlogout</code>. Persona calls these callbacks when a user logs in or out respectively. Like the callback to <code>get()</code>, the <code>onlogin</code> callback gets a signed assertion for you to verify.</p>
<p>You call <code>logout()</code> when a user logs out of your site, so Persona can update its state.</p>
<p>Because Persona maintains its own state with the Observer API, 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:</p>
<pre class="brush: js">
<code class="language-js"><span class="token keyword">var</span> signin <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById<span class="token punctuation">(</span></span><span class="token string">'sign-in'</span><span class="token punctuation">)</span><span class="token punctuation">;
<code class="language-js"><span class="token keyword">var</span> signout <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById<span class="token punctuation">(</span></span><span class="token string">'sign-out'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></span></code>

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(assertion) {
  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);
}
</pre>
<p>This code adds listeners which call <code>request()</code> and <code>logout()</code> 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 <code>watch()</code> to start listening for login and logout events. When Persona triggers <code>onlogout</code> the code updates its state to log the user out, and when Persona triggers <code>onlogin</code> the code sends the assertion to the server for verification.</p>
<p>Although the Observer API is more complex to use, it provides two extra features: <em>seamless first-time sign-in</em>, and <em>global logout</em>.</p>
<h3 id="Seamless_first-time_sign-in">Seamless first-time sign-in</h3>
<p>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 <a href="https://developer.mozilla.org/en-US/docs/Persona/Bootstrapping_Persona">fallback provider</a> 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 <em>will not</em> be automatically redirected to the website and signed in: they will have to navigate back to it.</p>
<p>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 <code>watch()</code> API will call the <code>onlogin</code> listener, and the user will be signed in.</p>
<h3 id="Global_logout">Global logout</h3>
<p>With the Observer API, the website listens to the <code>onlogout</code> event. If the user signs out of Persona by visiting <a href="https://login.persona.org/">https://login.persona.org/</a> 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.</p>
<p>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.&nbsp;</p>
Revert to this revision