If a single token is used for all APIs in a domain, you run the risk of leaking sensitive information to systems that do not need it or creating a powerful identity token that grants the holder access to many systems if it were to be compromised. (The same issues apply to any resource protected by OAuth 2, not just APIs. Likewise, they apply to any type of application that is attempting to consume such a resource.) In previous posts, I’ve touched on different approaches to this problem, but have never assembled all the information into one article — so, here we are.
For our purposes, let’s assume the following are true in our target environment:
- The OAuth 2 client is a native mobile application or a Single Page Application (SPA)
- The OAuth 2 client is accessing multiple APIs advertised on the same API Gateway
- The OpenID Connect (OIDC) authorization code flow with a public client is used (as described in my “Securely Using The OIDC Authorization Code Flow And A Public Client With Single Page Applications” post)
- The OAuth 2 client is using an authentication library to handle interaction with the identity provider (IdP). IdPs usually have a recommended library for each platform or client type
- Tokens are submitted from the client application to the API gateway as described in How To Submit Your Security Tokens to an API Provider Pt. 1 and How To Submit Your Security Tokens to an API Provider, Pt. 2, in line with RFC 6750
- Access tokens are JWTsAccess tokens are cached (by the authentication library) in the application (i.e., API consumer)
- Refresh tokens are cached (by the authentication library)
- Obtaining new access tokens (upon expiration of the current) via the refresh token grant are handled by the authentication library. More on this later
- Each API is protected with OAuth 2 scopes defined by Swagger, XACML, ALFA or similar. This is similar to Role Based Access Control
- The API gateway is validating scope information associated with access tokens
- An audience (as described in the OIDC Core spec for ID Tokens) is assigned to every access token. The API gateway is validating audience information maps to the requested API. What the audience represents is one of the main points discussed below
Now, let’s consider how the API consumer should handle access tokens. Should the API consumer (OAuth 2 client, OIDC relying party) obtain one access token that is used with all back-end APIs? Should there be one access token per back-end API? Is there another configuration that should be used?
We’re going to look at three different strategies that I have used at client sites before. There are several variables to consider:
- Number of APIs an access token applies to (via audience)
- Number of access tokens used by the application
- Audience usage (one per API, not used at all, generic/global)
- Scope (not used, generic scope, roles, actions on APIs)
The exact nature of these variables differs by organization, including the acceptable ranges for any of these parameters, the granularity of the audience, how audience and scope are used, etc., but these variables give rise to several patterns that could be employed to address how access tokens are utilized:
- Option #1: A single access token with a single “generic” or global audience that is applicable to all required APIs could be obtained from the IdP and submitted with each API call
- Option #2: A single access token with multiple audiences (one or more audiences for each API) could be obtained and submitted with each API call
- Option #3: Multiple access tokens could be obtained, each with its own audience relevant to one API that will be invoked
In all of these options, one or more scopes will be associated with the access tokens. If OIDC is being used, then the “openid” scope will be present in addition to any other scopes needed to access API endpoints. Likewise, instead of using scopes to describe user permissions on an API, a list of roles (maybe an array of strings) could be used.
Option #1: Single Access Token with Single Audience
The first option—a single token with a single audience that can be used against all desired APIs—is probably the most universally supported with out-of-the-box functionality. Included in this one access token are as many scopes as are needed to grant required permissions, obtain required claims, and perform other purposes for the various back-end APIs. That information will be visible to all downstream actors that intercept the access token. Depending on the use case, this could be an issue.
This approach is common and simple enough for all actors involved. There are several variations on this option, including:
- No audience information in the JWT access token. This is more or less the logical equivalent of the original option. Whether a Resource Server is going to accept an Access Token under these circumstances will be a matter of a digital signature validation and other checks normally required of a JWT. So, the collection of resources that can be accessed with the Access Token is defined exclusively by the trust of the signer certificate (of Access Tokens). This isn’t recommended, but has been done before.
- One audience definition in the IdP per application layer (maybe an internal API gateway and external API gateway). There is still only one entry in the JWT access token at runtime, but it is not a global audience. Token delegation may provide an additional layer of security here (see the next section for more information).
- One audience entry per environment (development, QA, etc.). Similar to the last, but what the audience represents differs.
The second and third variations could be combined to create a limited set of distinct audiences that represent application layers and environments.
The relationships between audience, scopes, access tokens and APIs in this use case are described in the following diagram:
Option #2: Single Access Token with Multiple Audiences
The second option—single access token, with multiple audiences covering all desired APIs—is allowed by the spec, but multi-audience JWTs acting as OAuth 2 access tokens isn’t universally supported by IdP vendors, API gateway vendors or other libraries. Just like with Option #1, there will be as many scopes in the access token as are needed to describe the permissions the user needs to interact with the APIs described by JWT audience list.
In a situation where token delegation is being used (i.e., the API gateway obtains a new access token that describes the authenticated user, but has a different audience, scope and claim information describing the downstream API Provider), the multi-audience token may provide significant simplification of the mechanics needed to obtain new tokens. This is because it wouldn’t necessarily need to obtain the new token for the downstream actor. However, this would likely be exposing additional information to other actors that wouldn’t necessarily have occurred otherwise.
The relationships between audience, scopes, access tokens and APIs in this use case are described in the following diagram:
Your application, the authentication library, IdP, API gateway and potentially other actors must be able to accommodate whichever path is chosen.
Some additional notes
- The Access Token an OAuth2 client receives may not reflect all requested scopes because IdP security configuration doesn’t allow it.
- All three options potentially involve multiple scopes assigned to Access Tokens.
- If a single generic API audience or multiple API audiences are used, then API providers may see more scope info than they otherwise would.
- If business roles are being used to define authorization policy, nothing has been lost in terms of security and information leakage.
- The application is registered as only one application (only one client identifier) with the identity provider. No approach that involves one application using multiple OAuth 2 client registrations is considered here (nor recommended).
- Regardless of which option is used to obtain access tokens, when they expire, new tokens can usually be obtained with a refresh token (except for the OAuth 2 Client Credentials Grant). If using the Client Credentials Grant, it should be easy enough to request additional tokens by replaying the original token request.