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 theclient_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 behttps://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 idscope
- 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-seenfxa-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:
- The resource server MUST verify that the
typ
header value isat+jwt
and reject tokens carrying any other value. - 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 - The
iss
claim MUST exactly matchhttps://accounts.firefox.com
- 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. Theaud
claim MAY contain an array with more than one element. The JWT access token MUST be rejected ifaud
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. - The current time MUST be before the time represented by the
exp
Claim.exp
is in seconds since Unix epoch. - The
scope
claim must contain the scopes necessary to access a protected resource. - If the protected resource requires a subscription, check the
fxa-subscriptions
claim for the expected subscription capability. More information on why thefxa-subscriptions
claim is used instead of adding subscriptions onto thescope
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.