Integration with FxA
Last updated: March 18th, 2026
Overview
Mozilla accounts integration is available for Mozilla groups on request using OpenID Connect (OIDC) and OAuth 2.0. Your service becomes a Relying Party (RP) that authenticates users through Mozilla accounts.
This tutorial covers integration steps. You must also meet ongoing integration requirements to maintain uninterrupted service.
What You Need to Provide
Before you can integrate, gather the following information:
| Item | Description | Required |
|---|---|---|
| OAuth Redirect URLs | Where FxA redirects after authentication (one for stage, one for production) | Yes |
| Webhook URLs | Endpoint to receive account events (password changes, deletions, etc.) | Yes |
| Service Icon | Displayed in users' Connected Services list. Confirm current specs with the FxA team. | No |
| Technical Contact | Email for API changes and critical updates | Yes |
| Expected Traffic | Rough estimate of authentication volume | Yes |
| Integration Type | Web app, native app, browser, API, or extension | Yes |
Quick Start (for experienced OAuth/OIDC developers)
If you're familiar with OAuth 2.0 and OIDC:
- File a Jira issue in the FXA project requesting OAuth credentials (include info from the table above)
- Join the firefox-accounts-notices group for updates
- Use staging endpoints for development:
- Authorization:
https://accounts.stage.mozaws.net/authorization - Token:
https://oauth.stage.mozaws.net/v1/token - Profile:
https://profile.stage.mozaws.net/v1/profile
- Authorization:
- Implement standard OIDC authorization code flow
- Set up webhook handler for account events
- Request production credentials when ready
Discovery document: https://accounts.stage.mozaws.net/.well-known/openid-configuration
How the Flow Works
Step-by-Step Integration Guide
Step 1: Request OAuth Credentials
File an issue in the FXA project in Mozilla's Jira with:
Required Information:
- OAuth redirect URLs (stage and production)
- Webhook notification URL
- Integration type (web app, native app, etc.)
- Technical contact email
- Expected traffic estimate
- Service icon
Tell Us About Your Needs:
- Do you need ongoing access to profile data when users aren't logged in? (requires refresh tokens with
access_type=offline) - Do you need to write profile data? (only
avataranddisplayNameare writable via the API) - Do you need encryption keys derived from the user's password? (scoped keys - note these change if the user changes their password)
- Will this be a subscription/paid service?
- Will you have subscription lead/marketing pages?
Most integrations are Relying Parties (user-facing apps where users log in). If you're building an API that other services call using tokens, you may be a Resource Server - see the distinction below.
You'll receive:
client_id- public identifier for your serviceclient_secret- private secret for backend token exchanges (never expose publicly or commit to repositories)
Note: Request separate credentials for each platform (e.g., web, iOS, Android).
Step 2: Implement the Authorization Flow
Build the authorization URL with these parameters:
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your OAuth client ID |
scope | Yes | Space-separated scopes (usually profile) |
state | Yes | Your random state token |
redirect_uri | Yes | Must match registered redirect URL |
response_type | Yes | Use code |
code_challenge | No | Only needed if using PKCE. (SHA256 hash of verifier) |
code_challenge_method | No | Only needed if using PKCE. Only S256 is supported |
access_type | Recommended | online or offline (offline gives refresh tokens) |
action | Recommended | email or force_auth |
entrypoint | Recommended | Metrics identifier (coordinate with FxA team) |
utm_campaign | No | |
utm_source | No | |
utm_medium | No | |
utm_term | No |
Step 3: Set Up Webhook Handler
FxA sends Security Event Tokens (SET) via webhooks when users change passwords, update profiles, change subscriptions, or delete accounts. You must handle these events.
Register Your Webhook
Create a pull request in cloudops-infra (FxA can help with this if needed):
- Stage: Edit projects/fxa/tf/nonprod/envs/stage/resources/eventbroker.tf
- Production: Edit projects/fxa/tf/prod/envs/prod/resources/eventbroker.tf
Add your client_id to endpoint_topic_config and webhook URL to endpoint_subscription_config. See example PR.
Verify and Handle Events
Webhooks are JWTs signed by FxA. Verify them using FxA's public keys from the JWKS endpoint (found via jwks_uri in the discovery document). Here is an example in typescript:
import jwt from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';
// Fetch and cache these at startup from the jwks_uri
let publicJwks: Array<{ kid: string; [key: string]: any }>;
// Your client_id, provided when you registered your OAuth credentials
const CLIENT_ID = 'your_client_id';
async function verifyWebhook(authHeader: string): Promise<object> {
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('Invalid authorization header');
}
const token = authHeader.slice(7);
const decoded = jwt.decode(token, { complete: true });
if (!decoded || typeof decoded === 'string') {
throw new Error('Invalid token');
}
const jwk = publicJwks.find(j => j.kid === decoded.header.kid);
if (!jwk) {
throw new Error(`Unknown key ID: ${decoded.header.kid}`);
}
return jwt.verify(token, jwkToPem(jwk), {
algorithms: ['RS256'],
issuer: 'https://accounts.firefox.com',
audience: CLIENT_ID,
});
}
app.post('/webhook', async (req, res) => {
try {
const event = await verifyWebhook(req.headers.authorization);
// Handle the event based on type
// Events: password-change, profile-change, subscription-state-change, delete-user
res.status(200).send('OK');
} catch (error) {
console.error('Webhook verification failed:', error);
res.status(401).send('Unauthorized');
}
});
Important: Resource Servers must return 200 even for unknown users. See event broker documentation for event formats.
Step 4: Test on Staging
Use staging servers for development. The staging database is persistent. Clean up test accounts when done - if using PyFxA, use destroy_account().
Verify:
- Users can authenticate and your app receives profile data
- Refresh tokens work (if using
access_type=offline) - Webhooks are received and processed correctly
- Error cases are handled gracefully
Production Checklist
Before going live:
- Request production OAuth credentials via your Jira issue
- Use production endpoints:
- Discovery:
https://accounts.firefox.com/.well-known/openid-configuration - OAuth:
https://oauth.accounts.firefox.com/
- Discovery:
- Register production webhook URL
- Submitted your service icon
- Joined firefox-accounts-notices mailing list
Reference
Profile Data
Mozilla accounts stores minimal identity data:
| Field | Description |
|---|---|
uid | Stable user identifier |
email | User's email address |
locale | Browser locale at account creation |
displayName | Optional display name |
avatar | Optional profile image URL |
Scopes
Most integrations need only scope=profile. For advanced scopes (Resource Servers, Sync access), see OAuth scopes documentation.
Relying Party vs Resource Server
| Relying Party | Resource Server | |
|---|---|---|
| What it is | User-facing app with login | API accessed by other services |
| User sees login? | Yes | No |
| Appears in Connected Services? | Yes, as distinct entry | No (shows as parent service) |
| Receives ALL account events (not just users who have signed in to that specific service)? | No | Yes |
| Examples | Websites, apps | Sync, Relay APIs |
Most integrations are Relying Parties. Resource Servers are typically internal Mozilla services accessed via Firefox's OAuth token.