Firefox OS/FirefoxAccounts

From MozillaWiki
Jump to: navigation, search

Developing with Firefox Accounts on FirefoxOS 2.0


Certified and (with Marketplace approval) Privileged Apps may use the native implementation of Firefox Accounts on Firefox OS. This implementation is based closely on the BrowserID API:

 - https://developer.mozilla.org/en-US/docs/Web/API/navigator.id

It has two important methods: watch() and request().

There are several changes:

 1) The API is exposed as navigator.mozId (not ".id").
 2) You MUST add a parameter to watch():
   + wantIssuer: 'firefox-accounts'
 3) You MAY add a parameter to request() which forces a password challenge:
   + refreshAuthentication: n // n MUST be a number >= 0; see below.
 4) Privileged apps firing request() for the first time will trigger a password challenge
     so that the user may demonstrate permission to grant the app access.

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:

 - https://github.com/mozilla/gecko-dev/blob/master/dom/identity/nsDOMIdentity.js#L825

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
     sendAssertionToMyServer(assertion).then(
       (result) => {
         if (!result.verified) {
           alert("Bad login: " + result.error);
           return;
         }
         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) post-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.
 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.

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 callabcks 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 refreshAuthentication parameter, if present, 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.