Skip to main content

JWT Access Tokens

Current as of October 17th, 2019

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 an access token

Firefox 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 is 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.

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.