Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

fx account client

Brian Warner edited this page Mar 22, 2014 · 1 revision

Using the FxAccounts object from within Gecko

(note: this text will move to the Mozilla wiki once it's stable)

The Firefox Accounts system enables a "sign-into-browser" action, where a user empowers a browser to use their Firefox Account by entering an email address and password. This is currently only used by Sync, but can be extended to other client code that lives inside the browser and needs to act upon the Account.

A user agent which starts with email+password and completes the onepw protocol dance gains the ability to request BrowserID-based signed certificates. These certificates can be used to produce BrowserID-like assertions for arbitrary audiences. The assertion can then be delivered (as a bearer token) to an external relying server, which can validate it, and grant authority to the sender (typically in the form of a session token).

The successful FxA client also learns two master encryption keys: kA (for "email-recoverable" data), and kB (for non-recoverable password-locked data). Client code can derive application-specific keys from these and use them to encrypt user data.

The "FxA Account Object" manages the protocol messages and user interaction. Gecko-side client code can ask this object for assertions and keys.

Sample Code, Interfaces Still Under Development

We're still working on making clean interfaces to the FxA client code. The current best reference is the Sync code which uses it:

services/sync/modules/browserid_identity.js

The FxA code itself lives in services/fxaccounts/FxAccounts.jsm.

Getting the Account Data

At any time, your code may call getSignedInUser() to learn about the currently signed-in user (if any):

Cu.import("resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");

    return fxAccounts.getSignedInUser().then(accountData => {
      if (!accountData) {
        this._log.info("initializeWithCurrentIdentity has no user logged in");

This promise will fire quickly: it only waits to load data from disk.

If accountData is null, then no user has signed in. Otherwise, it will be an object with the following properties:

   *          email: The user's email address
   *          uid: The user's unique id
   *          sessionToken: Session for the FxA server
   *          kA: An encryption key from the FxA server
   *          kB: An encryption key derived from the user's FxA password
   *          verified: email verification status
   *          authAt: The time (seconds since epoch) that this record was
   *                  authenticated

verified is a boolean, true if the account's recovery email has been verified (i.e. a link in the challenge email has been clicked). getSignedInUser will provide user data as soon as an account is configured, without waiting for it to be verified. But the client cannot get assertions or keys until verification is complete.

Your code should use uid as the primary identifier (e.g. when indexing stored data on a server). This will generally be in the form [email protected], but will vary in testing and staging environments. The email field currently contains the (exactly 1) recovery email address, but in the future, FxA may support users with multiple emails, or other recovery methods, and accounts may not have an email address at all. If you use email, be prepared for it to change in the future.

kA and kB are the master encryption keys. They must not be used directly: applications should use HKDF with a suitable per-purpose "CTXinfo" field to derive application-specific keys. kA will remain constant for the life of the account. kB will change if the account is ever reset (i.e. the user forgets their password, and performs the email-recovery flow).

authAt indicates the last time that the user's password was presented. Client code which initiates high-value transactions may want a "fresh" session (e.g. the password has been presented within the last 5 minutes). A future API will enable code to request this.

The sessionToken is for internal use, and should not be used by client code. It gives the FxA code the authority to request signed certificates.

If your code were to constantly poll getSignedInUser(), it would observe the following series of state transitions:

  • null
  • .verified = false
  • .verified = true but kA/kB = null
  • .verified = true, kA/kB are set

There are other functions which can be used to wait for these later states, but they are currently meant for internal use.

Getting Assertions

Once you have seen .verified = true, you can ask for an assertion:

fxAccounts.getAssertion(audience).then(assertion => {});

This is a BrowserID -style assertion, in which the certificate has the following fields:

It should be delivered over a secure channel to a server named by audience. That server can then verify it like regular BrowserID assertions, except that there is a dedicated issuer. FxA provides a new verifier service for FxA assertions that knows about these rules.

The FxA code manages keypair generation, certificate signing requests, and assertion signing.

Listening For Login Events

This is currently pretty ugly. Please don't use it.

When the user signs into the browser (currently driven by the "about:accounts" page and the Sync setup flow), a notification is sent to all interested observers.

// FxAccountsCommon.js doesn't use a "namespace", so create one here.
let fxAccountsCommon = {};
Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
 
const OBSERVER_TOPICS = [
  fxAccountsCommon.ONLOGIN_NOTIFICATION,
  fxAccountsCommon.ONVERIFICATION_NOTIFICATION,
  fxAccountsCommon.ONLOGOUT_NOTIFICATION,
];

onLogin fires when the user has finished signing into an account. It does not mean that the email address has been verified (the return value from getSignedInUser() will be non-null but accountData.verified is not guaranteed to be true). You could call .whenVerified(accountData) to get a promise that will fire when verification is complete. Account-management UI may need to distinguish between this signed-in-but-not-verified state and the fully-verified state, but regular clients should not. Regular clients should not do anything with the not-yet-verified state.

onVerification fires when the user has finished signing into an account and that account has finished email verification. It also waits until the kA/kB encryption keys are available.

The onLogout event fires when the user manually signs out of the browser. Currently, the UI for this is described as "disconnecting" your Sync account.

Neither onLogin nor onVerification will fire in a new browser session when the user finished signin and verification in a previous browser session. We do not yet have a good single tool for discovering when an account is available.

Currently, the way Sync manages this is to:

  • listen for onLogin and onLogout at browser startup
  • call getSignedInUser() at browser startup: if it indicates a user is signed in, it waits with .whenVerified(), then kicks off the rest of Sync. If no user is signed in, it does nothing.
  • when onLogin fires, it calls getSignedInUser() and waits with .whenVerified(), then kicks off the rest of Sync.

How this differs from sign-into-web

Note that this is (somewhat) distinct from "sign-into-the-web", for which the ...

Future Directions

Our plan is to replace the FxAccounts singleton with an "FxA object"...

There should be a single tool that client code can use to be notified when a fully-verified ready-to-go account is ready, that fires shortly after browser startup (for previously-established sessions) or later (when the user signs in in this browser session). A whenSignedIn() promise would do, but folks may not be comfortable with keeping a callback chain hanging around forever, which may never get used.

This tool should return a "FxA Object" which has easy to use methods like:

  • getFxAUID() -> "[email protected]"
  • getRecoveryEmail()
  • getKeysFor(subsystem) -> kA/kB derivatives
  • getAssertionFor(audience)