Join MDN and developers like you at Mozilla's View Source conference, 12-14 September in Berlin, Germany. Learn more at https://viewsourceconf.org

Firefox Accounts on Firefox OS

This article provides an overview of using Firefox Accounts in Firefox OS.

Top level overview

On Firefox OS 2.0+, certified and privileged apps may use the native implementation of Firefox Accounts, based closely on the BrowserID API. There are several differences:

  1. The API is exposed as navigator.mozId (not ".id").
  2. You MUST add an attribute to the "permissions" section of your Apps manifest.webapp:
    • "moz-firefox-accounts": {}
  3. You MUST add a parameter to watch():
    • wantIssuer: 'firefox-accounts'
  4. You MAY add a parameter to request() which forces a password challenge:
    • refreshAuthentication: n // n >= 0 See below.
  5. Sign-in status is atomic per device: the user is either signed in to FxA for every app or for none.

The source code is here: https://github.com/mozilla/gecko-dev/blob/master/dom/identity/nsDOMIdentity.js

watch()

Think of watch() as a constructor for an object to which you have limited access. You MUST call watch() exactly once to use FxA through navigator.mozId, and you MUST do so before calling request(). You are injecting event handlers into otherwise opaque, privileged code. You MUST include onlogin, onlogout, and onready; you should include onerror (source). A correct implementation of watch() (with silly handler bodies) looks like this:

  // during setup of your app
  this.loggedInUser = null;
  navigator.mozId.watch({
    wantIssuer: 'firefox-accounts',
    onlogin: function(assertion) {
      // You must implement the server functionality implied here - see https://github.com/mozilla/browserid-verifier
      sendAssertionToMyServer(assertion).then(
        (result) => {
          if (!result.verified) {
            alert("Bad login: " + result.error);
          }
          this.loggedInUser = result.emailAddress;
        }
      );
    },
    onlogout: function() {
      this.loggedInUser = null;
    },
    onready: function() {
      // FxA successfully initialized
    },
    onerror: function(error) {
      alert('FxA complains that: ' + error);
    }
  }

Three lobes of the state machine

The callbacks you pass to watch() fire stochastically from the perspective of your application, but you can group the firings into three categories:

  1. In response to watch():
    • FxA will fire onlogin() or onlogout() to tell you if there is currently a logged-in user, and then onready(). onready() doesn't tell you much except that FxA is working. You MAY wish to track whether onready has fired, and refrain from calling request() if it hasn't.
    • If your app is privileged and the user is accessing FxA from it for the first time, FxOS will surface a dialog asking the user to confirm granting this access:
      • In 2.0 and 2.1, the dialog will be a password challenge.
      • In 2.2, we hope to make it a simple OK/Cancel dialog.
  2. In response to a user or server action outside your event, such as:
    • The user chooses to sign out of FxA on the device.
    • The currently logged-in account has been deleted on the server.
  3. In response to your app's calling request().

request()

request() means:

  • Ask the user to demonstrate ownership of a verified FxA account.

You might want to read that sentence carefully; most of the individual words have implications for your app. Consider "ask":

  • If the user is not signed in, the device will throw up a sign-in/sign-up UX, which will cover your app's UI.
  • If the user closes the dialog before completing it (for example, because he has forgotten his password), and if you called request() with an oncancel parameter (see below), your handler will fire, telling you "the user said no".
  • If the user has already been asked (and done so by signing in), the device will send you that previous "yes" without telling the user it has done so, unless your app is privileged and this is its first call to request, in which case the user will get a password challenge asking them to grant access to their account by your app.

A correct implementation of request has one of two forms:

  1. request({oncancel: function() {console.log('User killed dialog.');}})
  2. request({oncancel: function() {console.log('User killed dialog.');},
             refreshAuthentication: gracePeriod}) // gracePeriod >=0

and will result in one of three callbacks being fired (assuming you defined them) after an unpredictable delay:

  1. The onlogin handler previously passed to watch(), meaning the user signed in or already was signed in.
  2. The onerror handler previously passed to watch(), meaning that something bad happened (e.g. network outage) or that the user's account is not verified.
  3. The oncancel handler passed to *this* invocation of request(), meaning that the user was presented with the sign-in UX, and manually closed it.

The optional refreshAuthentication parameter means, "ask the user to enter their password, unless the value is > 0 and they have entered it within that many seconds." For example, to support purchasing in an online store, you might decide that a first purchase requires entering a password, but additional purchases within 2 minutes do not. In that case you would pass:

  refreshAuthentication: 120

A value of 0 always raises a password dialog. Note that the grace period applies to any successful password challenge, not just one initiated by your app.

Verifying a Firefox Accounts assertion generated from FxOS

After receiving an FxA assertion from a FxOS device, the server should verify it in the following manner:

  1. Send the assertion to the Firefox Accounts BrowserID Verification API.  The production endpoint for this API is https://verifier.accounts.firefox.com/v2. This API has two required parameters:
    • assertion - the BrowserID assertion provided to your FxOS application
    • audience - the origin of your FxOS application, including the scheme. The expected audience must match the audience value embedded in the provided assertion, otherwise an error response will be returned.  
  2. If there is a problem verifying your assertion, the verifier will return an error response. Otherwise, it will return a success response. For FxA assertions, your server application must verify that the issuer in the success response matches the fully qualified domain of the Firefox Accounts authentication server. The domain of the FxA authentication server depends on which deployment of Firefox Accounts you are integrating with. For example, for the production FxA deployment, the expected issuer is api.accounts.firefox.com. Failing to check the issuer means that authorized parties can log in to your application as any user.
  3. The success response from the verifier also contains the user's FxA uid and verified email address.
    • uid - Firefox Accounts uses the legacy BrowserID format, which encodes the FxA uid in the "email" property of the BrowserID certificate, e.g., 92203e8678674f0ea2847b4f72f90dfe@api.accounts.firefox.com. This value is returned in the success response from the verifier and the FxA uid can be extracted from the local part of the email address, e.g., 92203e8678674f0ea2847b4f72f90dfe in the above example.
    • verified email address - The user's actual verified email address can be found in the fxa-verifiedEmail property of the idpClaims object returned in the success response from the verifier. User data in your application should be associated with the user's FxA uid, not the verified email address, which the user may be allowed to change in the future while retaining the same FxA uid.

 

 

Document Tags and Contributors

 Contributors to this page: chrisdavidmills, sampenrose, ckarlof, k88hudson, ferjm, jedp
 Last updated by: chrisdavidmills,