Skip to main content

Integration with FxA

Overview

Firefox Accounts integration is available for Mozilla groups on request. This integration is handled using OAuth 2.0, OpenID Connect, and webhooks for authentication, authorization, and receiving events regarding FxA users. Integrations with FxA assume the role of a Relying Party (RP).

Pre-Development

Before starting integration, please send a request to fxa-staff[at]mozilla.com to request a short meeting so we can all document our expectations and timelines. Please include answers to the following questions in the email:

  1. What type of Relying Party are you integrating? Examples would be, a web site, a native app, a browser, or an extension in the browser.

  2. Do you know how to implement OAuth?

  3. Will you need read access to a user’s profile data? See available profile data.

  4. Will you need ongoing read access to a user’s profile data? If necessary, a relying party can use a refresh token to query for a user's profile data, whether or not that user is logged in to the relying party. For example, if a user changes their email address and the relying party wants to update their local database with the changes.

    A refresh token is given out if access_type=offline when making the authorization request.

  5. Will you need write access to a user’s profile data? Only the avatar and displayname can be changed remotely. See the API documentation.

  6. Will you need to access Sync data? This is likely only needed for browser integrations. Most relying parties will not need this.

  7. Will you need encryption keys to encrypt user data? Optionally, when a relying party gets their access token they can also get a stable encryption key back (this is a scoped key). This key is derived from the user's password and if the user changes or forgets their password this key will change.

  8. Will your application display its own “enter your email” form? Providing your own form can give users a tight knit experience, but you'll need to send your own top of funnel metrics if you do.

  9. Who are the stakeholders?

  10. Who can be contacted for important updates, e.g., API changes?

  11. What is the schedule and key dates? At a minimum, when will QA start, when do you want to be live?

  12. Roughly, what amount of traffic do you expect?

  13. Will your integration provide a subscription service? If it will, please describe the products your integration will provide service for.

  14. Will your integration include subscription lead pages? These are generally marketing pages that include a link to start the subscription flow.

We communicate with our relying parties via the firefox-accounts-notices group. Please join this list to avoid any surprises.

OAuth Integration

Development

  1. Review the OAuth 2.0 documentation.
  2. Register for staging OAuth credentials by filing a deployment bug. See OAuth credentials.
  3. Your development servers should point to: https://oauth.stage.mozaws.net.
  4. User authentication follows the OAuth 2.0 protocol.
  5. Query parameters are set and validate when redirecting to Firefox Accounts.
  6. If you are hosting your own login form initialize and propagate the top of funnel metrics.
  7. User data and account notifications are properly handled and compliant with Firefox Account requirements.
  8. An icon suitable to display in Firefox Account’s Devices & apps list has been sent to Firefox Account developers. Please confirm with Firefox Accounts what the current requirements are.
  9. If multiple Resource Servers are accessed, create a distinct token for communicating with each server, limited to only the scopes required by that server. This may mean dropping your initial access token and using a refresh token to get additional, less privileged access tokens.

Preparing for Production

  1. Update your deployment bug asking for production OAuth credentials
  2. Production servers point to https://oauth.accounts.firefox.com/. Additional endpoints can be discovered dynamically at https://accounts.firefox.com/.well-known/openid-configuration.
  3. Someone from the FxA team has reviewed the integration code and tested the flow.

User Authentication with OAuth 2.0 / OpenID Connect in a nutshell

  1. Create a state token (randomly generated and unguessable) and associate it with a local session.
  2. Send /authentication request to Firefox Accounts. Upon completion, Firefox Accounts redirects back to your app with state and code.
  3. Confirm the returned state token by comparing it with the state token associated with the local session.
  4. Exchange the code for an access token and possibly a refresh token.
  5. If you asked for scope=profile you can fetch user profile information, using the access token, from the FxA Profile Server.
  6. Associate the profile information with the local session and create an account in the local application database as needed.

OAuth Credentials

  1. client_id - a public identifier that is used to identify your service. Can be public.
  2. client_secret - a private secret that is sent from the backend when interacting with the OAuth server. Must not be shared publicly, checked into a public repository, or bundled with compiled code.

Self Hosted Email-first Flow

  1. Initialize top of funnel metrics by calling /metrics-flow request with the required query parameters: 0. entrypoint This is a string identifying the source of the request and should be agreed upon by the Firefox Accounts team. 0. form_type This is either email or button depending on if you're self hosted email-first flow 0. utm_source 0. utm_campaign
  2. Propagate the email, flow_id and flow_begin_time query parameters, which are returned from the /metrics-flow request, in the request to /authentication.

To test without CORS errors using https://stable.dev.lcip.org/, your test application must have one of the following URLs:

Profile Data

Firefox Accounts only stores core identity data and associated profile information about users. Firefox Accounts does not store user data specific to relying services. Core identity data stored in Firefox Accounts includes:

  • a stable user identifier (uid)
  • the user provided email address
  • the user's locale provided by the browser during account creation
  • an optional display name
  • an optional profile image

/authorization query parameters

  1. client_id (required)
  2. scope (required). This is a space separated string. Review the list of scopes.
  3. state (required). This must be a randomly generated unguessable string.
  4. entrypoint (required). This is for metrics purposes and should represent the service making the request. This should be agreed upon by the Firefox Accounts team.
  5. email (required for self hosted email-first flow)
  6. flow_begin_time (required for self hosted email-first flow)
  7. flow_id (required for self hosted email-first flow)
  8. code_challenge (required for PKCE) This is a hash of a randomly generated string.
  9. code_challenge_method (required for PKCE) As of this writing only s256 is supported.
  10. action (suggested). This should be either email or force_auth.
  11. access_type (suggested). This should be either online or offline.
  12. utm_campaign (suggested)
  13. utm_source (suggested)
  14. utm_medium (optional)
  15. utm_term (optional)

Scopes

This will probably just be scope=profile for most relying parties, but there is further documentation.

User Data Hygiene

  1. Accounts should use uid rather than email address as the primary key. An account’s primary email address can change.
  2. Primary email changed notifications should update the contact email stored with the account.
  3. If profile information is stored, register for [#webhook-events] events or periodically refresh the profile information by using refresh token to create a fresh access token that can fetch profile information.
  4. Account deletion notifications should remove any server side data related to the user.
  5. Profile information should not be shared with 3rd parties without explicit consent.
  6. Destroy any outstanding access tokens and refresh tokens whenever a user signals their session or account should be terminated, e.g., the user signs out of your site, closes their account on your site, or unsubscribes from all functionality.

Webhook Events

If your integration includes an application service that stores profile information, you should create a webhook URL handler to handle Security Event Tokens (SET) for Relying Party events. These events will need to be verified using the FxA JWT keys that can be found from following the jwks_uri in the FxA well-known open-id configuration. For production, this URL is https://accounts.firefox.com/.well-known/openid-configuration.

The FxA JWT public keys should be retrieved from this URL at start-up, and used to verify the webhook JWT. The documentation on verifying a JWT for Step 1/2 are applicable to FxA JWT events.

If you're using TypeScript, an example of verifying a JWT is shown here:

import http from 'http';
import jwt from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';


function authenticate(request: http.IncomingMessage): object {
// Assuming this is how you retrieve your auth header.
const authHeader = request.headers.authorization;

// Require an auth header
if (!authHeader) {
throw Error('No auth header found');
}

// Extract the first portion which should be 'Bearer'
const headerType = authHeader.substr(0, authHeader.indexOf(' '));
if (headerType !== 'Bearer') {
throw Error('Invalid auth type');
}

// The remaining portion, which should be the token
const headerToken = authHeader.substr(authHeader.indexOf(' ') + 1);

// Decode the token, require it to come out ok as an object
const token = jwt.decode(headerToken, { complete: true });
if (!token || typeof token === 'string') {
throw Error('Invalid token type');
}

// Verify we have a key for this kid, this assumes that you have fetched
// the publicJwks from FxA and put both them in an Array.
const jwk = publicJwks.find(j => j.kid === token.header.kid);
if (!jwk) {
throw Error('No jwk found for this kid: ' + token.header.kid);
}
const jwkPem = jwkToPem(jwk);

// Verify the token is valid
const decoded: string | object = jwt.verify(headerToken, jwkPem, {
algorithms: ['RS256'],
});
if (!isIdToken(decoded)) {
throw Error('Invalid token format: ' + decoded);
}
// This is the JWT data itself.
return decoded;
}

Webhooks are processed from our event broker service. Currently, we emit events for password change, profile change, subscription change and delete account.

For additional documentation please reference the readme.

Register for webhooks

Once you have setup a service to receive webhook events, you can then register the webhook url by creating a pull request in cloudops-infra. To edit webhooks coming from FxA stage, you'll need to edit projects/fxa/tf/nonprod/envs/stage/resources/eventbroker.tf. To edit webhooks coming from FxA prod you'll need to edit projects/fxa/tf/prod/envs/prod/resources/eventbroker.tf. You'll need to add your client id to endpoint_topic_config, and your webhook url to endpoint_subscription_config. See an example PR.

Some flow diagrams

A full oauth flow