OAuth 2 Access Token Usage Strategies for Multiple Resources (APIs): Part 1
With the explosion of APIs, it's becoming more common for an application to consume a variety of different APIs, sometimes from different API providers. For example, consider a Single Page Application (SPA) that implements a shopping application for a retail operation. This SPA might call a shopping cart API, product information API, store location API, payment API, and other APIs, which may be hosted by the organization itself or multiple third parties.
Ideally, these APIs are all deployed with a common API gateway. (Granted, that doesn't always happen, especially in large organizations where each API may be supported by different teams that have made architectural decisions-or just good old-fashioned political decisions-independent of one another.) To create a common API security model that spans all APIs advertised on an API gateway, let's assume all endpoints require an OAuth 2 Access Token issued from a common identity provider and have the appropriate API security token checks in place.
Fundamental usage questions must be addressed regarding how the OAuth 2 access tokens are employed. One key question we explore in this series is:
Should the application obtain a single access token that is used against all APIs, use multiple access tokens where each has the limited information needed for a particular API, or do something in between?
Three Approaches for OAuth 2 Access Token Usage
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 backend 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:
Option #3: Multiple Access Tokens
The third option-using multiple access tokens-involves obtaining a unique access token for each API that needs to be accessed by the application. Each of these access tokens will have a single audience that describes one API. Each token (associated with each API) will have some number of scopes associated with it to describe the user's permissions on the corresponding API. Supporting multiple access tokens (and possibly other token types) is more complex for all actors involved, but especially the application and authentication library.
The authentication library must be able to support obtaining and caching the various tokens that are obtained. The application needs to keep track of which permissions are required for which function.
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.
Which Approach Should You Implement?
So, we have presented three approaches to using OAuth 2 access tokens with multiple APIs deployed with the same API gateway. As with so many topics we've discussed, the single greatest determining factor of the best path forward will likely be what your technology stack (identity provider and API gateway) supports. Keep this in mind when defining your strategy.
In large enterprise organizations, there tend to be identity and access management, enterprise architecture, and/or information security teams with strong opinions about how these types of details should work. Those opinions tend to vary wildly, but will greatly influence the chosen solution.
In the rest of this series, we will explore some foundational concepts that impact the approaches, examine how to implement these options, and consider which is the best approach under different circumstances.