OAuth2 Token Exchange in Practice
Over time, OAuth2 has evolved to meet increasingly complex security needs that go beyond the basic capabilities of a Security Token Service (STS). In this post, we will discuss the Token Exchange grant type which is a versatile grant type designed to tackle a range of real-world challenges.
At its core, Token Exchange facilitates client applications obtaining new security tokens by exchanging them for previously acquired tokens. What’s interesting is that this previous token can come from the same Identity Provider (IdP) that issues the new token or originate from a different IdP in a trusted domain.
But why should we care about the Token Exchange grant type? Let’s dive into a couple of practical scenarios to see how it can make a difference. But before that, let’s explore two important concepts — impersonation and delegation.
Impersonation Vs delegation
Let’s introduce our imaginary friend, Bob, a music enthusiast who has meticulously curated a collection of super cool playlists on an online music streaming platform. Bob takes great pride in his playlists. Now, enter Alice, a close friend of Bob’s, who seeks access to his playlists to find the perfect soundtrack for her upcoming road trip. Let’s examine their interaction within the streaming platform.
In this case, Alice is impersonating Bob:
- The access credentials provided to the streaming platform don’t contain any information to recognize Alice’s identity.
- Whenever Alice accesses the platform, it believes Bob is the one using the service although the credentials (e.g. — a short-lived access token) used by Alice to access the service are different from Bob’s credentials.
Over time, the music streaming service has introduced features to support family and friend sharing. In this evolved scenario, Alice can pass her identity information along with the temporary pass provided by Bob. So the scenario can be considered as delegation now.
In the case of delegation:
- The access credentials to the streaming service contain information to recognize Alice’s identity in addition to Bob’s identity.
- When Alice accesses the service, it distinguishes between Bob and Alice, acknowledging Alice as Bob’s agent.
Now that we understand these concepts, let’s explore the practical use cases of the Token Exchange.
1. API Gateway Calls Backend API on Behalf of a Client
Imagine a scenario where an application possesses a security token from the IdP to access an API Gateway (API-GW) obtained through a prior interaction between the IdP and the end user. However, this existing security token, while sufficient for API-GW access, may not grant access to the backend API due to audience and scope restrictions. For instance, if the token is a JWT, its audience (aud) may be limited to the API-GW, and its scopes may be specific to the API-GW only. While one workaround might involve including the backend API audience and scopes in the token, this is not ideal from an API security perspective.
The token shown in the diagram above would be valid for the API-GW since it has the right audience value for the API-GW and includes the required scopes defined by the API-GW. However, if you try to use the same token with the BE-API, it won’t work because the token isn’t intended for the BE-API and will be rejected by the BE-API after looking at the audience claim.
In this case, the right approach would be for the API-GW to request a new security token suitable for calling the backend API from the IdP by trading the current token sent by the client application. The Token Exchange grant type facilitates this exchange, allowing the API-GW to request the audience and request scopes from the backend API.
The API-GW has the flexibility to choose between impersonation and delegation. In impersonation, the API-GW keeps its identity hidden from the backend API and acts as the end user accessing the API through the client application. In delegation, the backend API recognizes that the API-GW is acting on behalf of the client application, which has obtained authorization from the end user. The choice between these approaches depends on the specific design, business and security requirements of the system.
2. Microservice Calls Another Downstream Microservice
This scenario closely resembles the previous one, with the primary difference being that the interaction occurs between two microservices, omitting the API-GW. Within a well-designed microservices security architecture, microservices only accept tokens if the tokens are intended to be used by the specific microservice typically with the correct audience and scope claims. Similar to the previous use case, the initial security token may not grant sufficient access to the downstream microservice, necessitating a Token Exchange call.
Again, the token shown in the diagram above is valid for the Customer-Service microservice because it has the right audience value for the Customer-Service and includes the required scopes as well. However, if you try to use the same token with the Order-Service, it won’t work because the token isn’t intended for the Order-Service and will be rejected by the Order-Service after looking at the audience claim.
3. Upgrading or downgrading the Scope of Security Tokens
Security best practices emphasize the principle of least privilege, within the context of our current discussion, applications should initially request a token with the minimal scopes required for current functionality, without making assumptions about future functionality that might require additional scopes. For instance, a mobile banking app might request a security token initially with read-only scopes for account data, such as balances and statements. When a user intends to perform a transaction like a fund transfer, the app can request the IdP to issue a new token with additional scopes. Token Exchange is a preferred solution for scope upgrades, as opposed to making duplicate token requests or using refresh tokens. The same would be applicable to scope downgrading too, as soon as an application has completed a transaction that requires a higher level of authorization such as in a fund transfer transaction, it should exchange an existing token for a token associated with a limited number of scope to avoid unwanted risk.
In some cases, an IdP may be configured to issue an IDToken during user login while delaying the issuance of an access token until the user engages in specific actions, such as transactions. Token Exchange becomes practical in such scenarios, allowing the application to obtain the required access token with required scopes when needed.
4. Application Accessing Resources in a Different Trust Domain
Consider a situation where an application possesses an access token issued by the default IdP within its trust domain, which could be the same organization or department. Now, the application needs to access an API called Loyalty API residing in a different trust domain, possibly maintained by a partner organization and secured using a different IdP called Loyalty-IdP. The application must obtain a security token from the Loyalty-IdP to access the Loyalty API. This is another use case perfectly suited for the Token Exchange grant type. However, compared to previous scenarios, this involves two trust domains and two IdPs, necessitating the existence of a trust relationship between these IdPs.
5. Workload Identity Federation
While this scenario shares similarities with the previous use case, I wanted to consider it as a separate use case due to the growing adoption of Workload Identity Federation patterns. Unlike traditional on-premise infrastructure, workloads deployed in cloud environments, such as AWS, Azure, GCP, or Kubernetes receive some notion of an identity from the cloud provider typically in the form of a token. In this scenario, an application or service deployed in a cloud environment seeks to access a Loyalty API deployed by a partner organization in a different trust domain. Since the Loyalty API operates in a separate trust domain, the identity token issued by the underlying cloud provider isn’t sufficient. The workload must exchange its identity token with the Loyalty-IdP to obtain a new access token and access the Loyalty API. Token Exchange is an ideal solution for this scenario, and major cloud providers already support this pattern.
As in previous use cases, establishing trust between the Loyalty-IdP and the cloud provider’s IdP is essential. For further details on the Workload Identity Federation, stay tuned for my upcoming post dedicated to this topic.
Now that we’ve gained a solid understanding of the potential use cases for the Token Exchange grant type, it’s time to delve into the specifics of how this grant type works. One important point to mention, the Token Exchange grant type is very flexible, it lays out request and response messages, along with the message flow. However, it leaves certain crucial aspects open for implementation decisions, such as the supported token types and trust relationships, which are particularly important in cross-domain scenarios.
Request, response, and the flow
We will start by exploring various use cases, beginning with the scenario where token exchange occurs within the same trust domain, facilitated by a single Identity Provider (IdP).
- In this scenario, an application initially acquires an access token. This token is obtained either by redirecting the end user to the IdP or by using the client credentials grant type. The received access token is then presented to the API-GW during an API call. The audience of this token is set to “api-gw,” and its scopes are aligned with those related to the API-GW.
- After validating the received access token, the API-GW creates a Token Exchange request and initiates the exchange flow by sending it to the IdP. Here, in the context of Token Exchange interaction, the API-GW plays the role of the OAuth2 client. This is an example where an API-GW that is generally recognized as a Resource Server (RS) within the OAuth2 terminology is designated as an OAuth2 client within the context of the Token Exchange. In the bottom line, depending on the circumstance, an entity different from a traditional OAuth2 client (e.g. — web and mobile apps) can participate in the Token Exchange by playing the client role.
- As the next step, the IdP validates the Token Exchange request and issues a fresh access token. This new token has “be-api” as its aud (audience) claim value and includes the requested scope within the scope claim. The API-GW can request necessary scope values to access the BE-API using the scope request parameter.
- The API-GW utilizes the newly received access token to successfully call the Backend API (BE-API).
As per the Token Exchange specification, the following parameters are mandatory in the request:
- grant_type: This parameter signals to the IdP that this is a token exchange request.
- subject_token: This parameter carries the current token received from the client, intended for exchange.
- subject_token_type: Denotes the type of the subject token (e.g., OAuth2 access token, OIDC IDToken, or SAML token).
- requested_token_type: (Optional) This parameter allows the client application to specify the desired token type. In the absence, the IdP may determine the token type based on the application’s configuration.
While not mandatory, parameters such as audience, resource, and scope are valuable for providing essential information about the target backend service to the IdP. This information helps the IdP identify the appropriate access policy for the target backend.
As shown in the diagram above, the ‘resource’ parameter typically points to the location of the target, often represented as an HTTP URL. Meanwhile, the ‘audience’ parameter is reserved for the logical name associated with the target, a logical target name recognized by the Identity Provider (IdP). To specify the necessary scope for the target, you can include these scopes within the ‘scope’ request parameter.
Here is a sample Token Exchange request message that I used with Asgardeo — a cloud identity provider from WSO2.
In the impersonation use case demonstrated above, to employ Token Exchange in delegation, two additional parameters are required: actor_token and actor_token_type. These parameters provide identity information about the party requesting a new token. For example, a customer-service application intending to call an order service with an exchanged token indicates its identity by passing these parameters.
The two additional parameters used in delegation:
- actor_token: The token that contains the information related to the identity of the token requesting party.
- actor_token_type: Denotes the type of the actor token.
The specification does not define any specific criteria for subject tokens to be considered valid for the token exchange. Nevertheless, it introduces the may_act claim, which an IdP could utilize to ascertain whether the requesting party is authorized to exchange their current subject token for a delegated token. This authorization can be verified by cross-referencing the may_act claim with either the requesting client’s identity or the identity information of the actor token.
Implementation of Token Exchange varies: some might insist on the presence of the may_act claim in the subject token as a mandatory requirement, while others might consider it optional. Additionally, there’s no standardized method for configuring and including may_act in the initial token destined to become the subject token in a subsequent stage. One possible approach could be treating the may_act configuration as a client-specific setting in the IdP.
The Token Exchange specification does not impose specific requirements for client registration or client authentication. However, in practical implementations, client authentication, often involving client secrets or other methods, is commonly employed to achieve the desired protection.
The Token Exchange response closely resembles standard OAuth2 responses but includes two additional parameters:
- issued_token_type: This parameter signifies the type of the issued token, aligning with parameters like requested_token_type, subject_token_type, and actor_token_type from the request.
- token_type: Unlike the previously mentioned parameters, this parameter informs the client how to use the access token to access the target resources. For example, a value of Bearer indicates that the issued security token can be presented as is, without additional proof of eligibility.
Also, the access_token parameter in the response can be used to return various token types, not limited solely to access tokens. Here is a sample Token Exchange response message which returns an access token.
Now, let’s move into a cross-domain use case that we discussed above which involves two different IdPs.
- In this scenario, the Sales app initially acquires an access token from the Sales-IdP. This token allows access to the required API calls within the Sales-API.
- When the application needs to call the Loyalty-API to complete a business transaction, it creates a Token Exchange request. This request utilizes the current access token issued by the Sales-IdP.
- The Loyalty-IdP validates the received Token Exchange request. Notably, Loyalty-IdP recognizes the subject token’s origin, issued by the Sales-IdP, and proceeds to validate the token’s signature. While the specification does not prescribe a trust model or verification mechanism, a widely used approach is the JSON Web Key Set (JWKS), which contains public keys for signature verification. To establish trust, Loyalty-IdP should be configured with a JWKS URL from the Sales-IdP. However, it’s possible for an Idp to use different mechanisms to obtain the public keys of the other IdP involved such as directly uploading public keys.
- Upon successful verification, Loyalty-IdP issues a new access token, tailored to the correct audience and scope values required to access the Loyalty-API.
- The Sales app utilizes the newly received access token to seamlessly call the Loyalty-API and complete the intended business transaction.
The act claim
When delegation is used, sometimes it’s important to indicate that the delegation has occurred and who is the acting party. This is achieved through the use of the act claim by the IdP, which plays a role in signaling that delegation has occurred and provides essential information about the identity of the acting party. The act claim can be included in the introspection response of a delegated token, serving a consistent purpose throughout the token exchange process.
One noteworthy feature of the act claim is its structure as a JSON object. This structural flexibility is significant because it allows the act claim to capture a delegation chain by nesting act claims within one another. However, when making policy decisions, only the outermost act claim should be considered and the nested act claims are to provide information about the delegation chain. This nesting capability is quite important in token-based systems to represent complex delegation scenarios when involving multiple entities.
As we explored a couple of practical applications of the Token Exchange grant type in detail, I hope you gained some understanding of how to leverage this grant type in real-world scenarios.
Stay tuned for more insights into the world of OAuth2 and identity management, where we’ll continue to bridge some gaps.