Skip to main content

Tokens

Last updated: April 17, 2023

Token Formats

A Bearer Token is an opaque string that can be used by any client in posession of the token, simply by presenting it verbatim in the request to the server (e.g. in the Authorization header). These are the simplest types of token for clients to use, but allow for interception and replay attacks. In particular, the server that receives a Bearer Token is capable of extracting it and using it to perform other API calls.

A Hawk Token consists of an opaque string id and corresponding secret key. To use the token, clients must use the secret key to sign outgoing requests using the scheme described here. This is more complicated for clients but it binds the use of the token to the particular request being made, and it offers the potential for replay prevention.

An FxA Hawk Token is a 32-byte string from which the id and key of a Hawk Token can be generated using HKDF. This simplifies client handling of the token since it only has to persist a single value.

Token Types

Mozilla accounts uses a number of different types of tokens to keep track of whether a user has been authenticated, and of what actions a client is authorized to perform.

The most important types of tokens for FxA Replying Parties to know about are:

  • Session Tokens, long-lived tokens that represent an authenticated user session with the FxA servers.
  • OAuth Refresh Tokens, long-lived tokens indicating that the user has authorized a particular client to perform particular actions on their behalf.
  • OAuth Access Tokens, short-lived tokens used by authorized clients when performing particular actions on behalf of the user.
  • OIDC Identity Tokens, short-lived JWTs used to communicate an authentication event from FxA to a relying party.

FxA has additional types of tokens that it uses for its own account-management purposes, but which should never be visible to Relying Parties:

Relying Parties that wish to integrate with Firefox Sync may also need to know about these additional types of token:

Session Tokens

A sessionToken is the Mozilla accounts equivalent of traditional website login session cookie - it represents the user who is currently signed in to accounts.firefox.com, and it used to authenticate many calls from the FxA front-end website to the FxA back-end API.

Session tokens are an FxA Hawk Token where the 32-byte string is generated by the server. They can be obtained by:

  • The successful creation of a new account, via the /account/create endpoint.
  • A successful login to a new account, via the /account/login endpoint.
  • Duplicating an existing sessionToken, via the /session/duplicate endpoint.
  • Providing the id of a previous sessionToken when changing the account password, via the /password/change/finish endpoint.
  • Requesting the https://identity.mozilla.com/tokens/session scope during an OAuth flow.

Session tokens are intended only for authenticated communication with the FxA servers themselves, so RP servers should never encounter a session token in the wild.

On the client side, session tokens are typically handled by web content on accounts.firefox.com while the user is interacting with their account. However, web browsers that support signing in with FxA in order to access account-enabled browser features, may obtain a session token during the sigin flow and use it to communicate directly with the FxA server APIs.

Session tokens in MySQL can be associated with a device record. “Device” is a misleading name here because it really means “Firefox instance”.

Verified Sessions

As an additional security measure against credential-stuffing attacks, FxA has the notion of a "verified session", which is intended to indicate a higher level of confidence in the authenticity of the user.

A session is considered verified if the user did some additional authentication step in addition to proving knowledge of the account password, such as completing an email confirmation loop or answering a 2FA challenge. Sessions may also be considered verified based on server-side heuristics, such as whether the account is newly-created or whether we've seen previous verified logins form the same IP address.

Certain actions on the account can only be performed with a verified session, including:

  • Modifying email or 2FA settings
  • Authorizing OAuth grants that involve key-bearing scopes

Multi-Factor Auth / 2FA

If the user enables two-step authentication on their account, then each sessionToken will keep track of whether it has been involved in a successful 2FA authentication. This information is reported in the session metadata in two ways:

  • The "authenticator assurance level" or "AAL" is a numeric indicator of the number of distinct types of authentication factor that have been provided for a session; it will be 2 for sessions in which the user has successfully successfully performed 2FA.
  • The "authentication methods" is a list of all the different types of authentication action that have been performed on this session (such as "entered password" or "completed email loop" or "provided totp code"). It's a more detailed view than the summary provided by the AAL.

OAuth Refresh Tokens

RPs that wish to obtain long-lived permission to access the user's account data should request an OAuth Refresh Token as part of their authorization flow. This is a long-lived Bearer Token that represents the permissions granted by the user to that RP. For example the refresh token might indicate that the RP has permission to read the user's profile data.

Refresh tokens can only be created by authorizing them via a valid sessionToken.

Refresh tokens should only ever be presented to the FxA OAuth server, and should not be used when talking to other resource servers. Instead, the RP should use the refresh token to generate a short-lived access token and use that to communicate with resource servers.

OAuth Access Tokens

RPs that wish to access resource servers on behalf of the user (say, to read or store user data) need to obtain an OAuth Access Token. This is a short-lived Bearer Token that represents the permissions granted by the user to that RP, in a format that can be consumed by resource servers. For example the RP might use an access token with scope "profile" in order to read the user's profile data from the FxA profile server.

FxA also supports JWT Access tokens. See JWT Access Tokens for an in-depth look at that less-commonly-used type.

OIDC Identity Tokens

They're for signin.

See the OIDC spec and the FxA supported claims.

This token, a JWT, proves that a user's been authenticated (in this case, with FxA). It can also be used as the id_token_hint query param value in a prompt=none flow (additional information).

This token is issued along with the access token and refresh token when the scope "openid" is present.

KeyFetch Tokens

They're for fetching keys. RPs shouldn't need to know this, because they use the scoped-keys flow.

Password Change Tokens

A special-use Hawk token for changing password.

Password Forgot Tokens

This is a special-use token for resetting a password. The user needs to be verified via a code received over email. This has a limited number of verification attempts and generates a reset token.

Account Recovery Tokens

To help reduce the risk of a user losing their encrypted data (such as synced passwords or bookmarks) during a password reset, Firefox Account users can create an "account recovery key". This account recovery key is used to store an encrypted version of the user's encryption key kB, which can be re-wrapped with their new password during the password reset process.

tip

If you're looking for more detail, see the onepw protocol spec

Registering an Account Recovery Key

Creating a new account recovery key involves the following steps:

  • FxA web-content prompts for the user's password and retrieves kB.
  • FxA web-content generates an "account recovery key", consisting of between 16 and 32 random bytes.
  • FxA web-content uses HKDF to derive two values from the account recovery key:
    • A key fingerprint: recover-kid = HKDF(recover-key, uid, "fxa recovery fingerprint", len=16)
    • An encryption key: recover-enc = HKDF(recover-key, uid, "fxa recovery encrypt key", len=32)
  • FxA web-content encrypts kB into a JWE to produce the "recovery data":
    • recover-data = JWE(recover-enc, {"alg": "dir", "enc": "A256GCM", "kid": recover-kid}, kB)
  • FxA web-content submits recovery data to FxA server for storage, associating it with the fingerprint (recover-kid)
    • POST /recoveryKey, providing recoveryKeyId and recoveryData in the request body.

This scheme ensures someone in possession of the account recovery key, can request the encrypted recovery data from the FxA server, and use it to recover the user's kB.

Using an Account Recovery Key during password reset

During the password reset process, a user who has an account recovery key can use it to maintain their existing kB as follows:

  • User completes an email confirmation loop to confirm password reset.
    • This produces an accountResetToken, a credential for subsequent requests.
  • User retrieves backup authentication code from print out or downloaded file, and provides it to FxA web-content in the password reset flow.
  • FxA web-content uses the backup authentication code to derive the fingerprint and encryption key (recover-kid and recover-enc as defined above).
  • FxA web-content requests recover-data from FxA server, providing recover-kid.
    • GET /recoveryKey/:recoveryKeyId, authenticated with accountResetToken.
    • Providing the :recoveryKeyId here proves that the user possesses the account recovery key, while the accountResetToken proves that they control the email address of the account.
  • FxA web-content decrypts recover-data with recover-enc to obtain the user's kB.
  • User enters a new password into web-content.
  • FxA web-content wraps kB with the new password, and submits wrapKb and recoveryKeyId with the account reset request.
    • POST /account/reset, authenticated with accountResetToken, providing recoveryKeyId and wrapKb in the request body.
  • Upon successful password reset, the FxA auth-server deletes the account recovery key and its associated recovery data.

BrowserID Assertions

warning

BrowserID stopped being used in Firefox 78 and we're eagerly awaiting the long tail of users to upgrade so we can remove this code. See details in Issue #9007 / FXA-2715.

Legacy identity assertion format. Used by sync, and accepted when granting OAuth tokens, but should not be used for anything new.

JWT Access Tokens

The original OAuth 2.0 spec does not specify a format for access tokens, most OAuth implementations use fixed length opaque tokens.

A service provider (SP) that accepts access tokens must verify the token to determine whether the grant associated with the token has sufficient privileges to access a protected resource. With opaque tokens, verification involves sending the token to the remote authorization server (AS)'s /introspect endpoint. The AS looks up the token in a database, verifies the token is still valid, and returns associated metadata such as a userid and list of scopes in the grant. Because verification involves a request to the AS, verification can introduce unwanted latency.

The IETF JWT access token draft spec solves the latency problem by defining a standard access token format that can be verified locally by the SP, eliminating the need to call remote services on every verification.

JWTs are signed JSON packets that can contain arbitrary information in fields called claims where a claim is a single unit of information. JWT access tokens are signed with public/private key pairs, and once the SP fetches the correct public key from the AS, no further remote calls are needed. JWT access tokens can contain most of the same information as the AS's /introspect endpoint.

At a high level, a SP that wishes to do local verification of JWT access tokens needs to verify the header of the token, verify the signature of the token, ensure the token is not yet expired, and ensure the access token contains the correct claims necessary to access a protected resource. More information can be found in the section titled Local verification of a JWT access token.

Claims within a JWT access token

Mozilla accounts largely follows the IETF JWT access token draft spec's Data Structure.

  • aud - audience. By default the client_id of the Relying Party (RP). If a RP specifies a Resource Indicator when requesting the access token, it will be an array that contains both the client_id and the resource indicator.
  • client_id - client id of the RP.
  • iss - issuer. For tokens that come from FxA's production stack this will be https://accounts.firefox.com
  • exp - expiration time after which the access token MUST NOT be accepted for processing, in seconds since Unix epoch.
  • iat - time at which the token was issued, in seconds since Unix epoch.
  • jti - JWT id
  • scope - space separated list of scopes associated with the grant.
  • sub - subject, user id. Normally the FxA user id for the user. If the RP uses Pairwise Pseudonymous Identifiers (PPID), will be an identifier that cannot be correlated to either the real FxA userid, or the PPID given out to other RPs.
  • fxa-subscriptions - space separated list of subscriptions the user has for the RP. Claim is only present if the user has a subscription with the RP.

Internal Mozilla reliers also have access to the following custom claims:

  • fxa-generation: A number that increases each time the user's password is changed. Reliers can use this value to reject JWTs that were created before the password change, by tracking the largest-seen fxa-generation claim for each user and rejecting tokens that have smaller values.
  • fxa-profileChangedAt: A timestamp that increases each time the user's core profile data is changed. Reliers can use this value to refresh cached profile data in a timely manner.

Local verification of a JWT access token

The following is based on the section on validation JWT Access Token Draft Spec:

  1. The resource server MUST verify that the typ header value is at+jwt and reject tokens carrying any other value.
  2. The resource server MUST validate the signature of all incoming JWT access token according to RFC7515 using the algorithm specified in the JWT alg Header Parameter. The SP MUST use the keys provided at https://oauth.accounts.firefox.com/v1/jwks
  3. The iss claim MUST exactly match https://accounts.firefox.com
  4. The resource server MUST validate that the aud claim contains the resource indicator value corresponding to the identifier the resource server expects for itself, and should at a minimum contain the client_id for the RP. The aud claim MAY contain an array with more than one element. The JWT access token MUST be rejected if aud does not list the resource indicator of the current resource server as a valid audience, or if it contains additional audiences that are not known aliases of the resource indicator of the current resource server.
  5. The current time MUST be before the time represented by the exp Claim. exp is in seconds since Unix epoch.
  6. The scope claim must contain the scopes necessary to access a protected resource.
  7. If the protected resource requires a subscription, check the fxa-subscriptions claim for the expected subscription capability. More information on why the fxa-subscriptions claim is used instead of adding subscriptions onto the scope claim, please see this explanation.

Libraries such as node-jsonwebtoken, when properly configured, take care of all of this except for the scope and fxa-subscriptions checks.

Remote verification of a JWT access token

A SP is not required to locally verify JWT access tokens, instead it may verify these tokens by presenting them to FxA's /introspect and /verify endpoints.

Pitfalls of JWT access tokens

While FxA JWT access tokens allow for local verification, they do have some drawbacks.

Access token size

The most obvious drawback is size, a JWT access token requires over 800 bytes whereas a normal access token requires 64 bytes. JWT access tokens that are sent with every request could add significant overhead.

JWT access tokens are signed using RSA keys, which generate large signatures. Changing to a different key type would reduce the signature size but due to lack of demand this is not on the FxA roadmap.

Revocation and cached tokens

A second, more subtle issue comes with local verification and revocation. When a token is verified against FxA servers, FxA is able to look up whether that token has been revoked by the user and immediately notify the SP the token is no longer valid. SPs that cache and locally verify JWT access tokens have no way of knowing whether the token has been revoked, they can only determine whether a token has expired. Because tokens expire 24 to 48 hours after they are created (depending on FxA server load), an SP could consider a token valid long after it has been revoked by the user.

Two mechanisms exist to partially mitigate this:

  • Perform an occasional remote verification against the /introspect endpoint.
  • When trading the code for the token, specify a short ttl. Whenever the refresh token is used to fetch a new access token, the /token endpoint will return a 4xx error indicating the refresh token has been revoked.

In the future, FxA may send notifications to SPs whenever an access or refresh token is revoked, but this functionality is not yet built.

Requesting use of JWT access tokens

JWT access tokens are only enabled for RPs on an opt-in basis. RPs can request JWT access tokens when their OAuth credentials are being provisioned.

Token Pruning

FxA has a token pruning script which runs via cron and is responsible for removing stale tokens in our system. The actual token limits and values are variables which we may modify at any time and aren't in the repository.

A session token that has a device record will never be pruned, because that would break Sync. So we only prune tokens if there’s no corresponding device in the db.