Token exchange is a protocol extension for OAuth 2.0 in which an authorization server can act as a security token service (STS) that provides security tokens, defined in RFC 8693.
As the adoption of automation grows, so do machine-to-machine connections, and AI agents take this model to the extreme. To keep all these interactions under control, you need a model that is based on global access policies rather than individual permissions for each agent.
Token exchange is one piece of this puzzle, allowing OAuth clients to obtain broader access on demand, lasting only as long as they need it.
In this article, we’ll explore token exchange in OAuth 2.0 for scenarios such as delegation and identity federation.
How Does OAuth Token Exchange Work?
Token exchange has been designed as a simple flow to add minimal workload overhead:
- An OAuth client needs access to a new resource.
- The client requests an access token for the new resource, providing an existing token.
- The authorization server evaluates its access policies.
- If the request aligns with the policies, the authorization server returns a new access token.
The OAuth client that the user is interacting with is not necessarily the one requesting the exchange. It’s more frequent in Resource Servers accessing other resources, and acting as OAuth clients.
The three main use cases for token exchange are:
- Policy-driven access: Provision clients with minimal access, and provide broader access via token exchanges.
- Delegation: An agent serving multiple users, obtaining access tokens to act on behalf of each user.
- Federation: Agents requesting access to external resources.
Let’s see them in action with some examples.
Delegation and Impersonation With OAuth Token Exchange
If you have an AI agent that serves multiple users and need to access external resources, you have three choices:
1. You grant the agent access to all resources: But then the agent is overprivileged, being able to access resources at any time. If the agent is compromised, a malicious actor could exploit all this access.
2. The user shares their access tokens with the agent: The agent can act as the user, but then the user has no guarantee that the agent won’t store its credentials for longer than needed. Also, there is no transparency on who is accessing the resources. Finally, if the agent is compromised, the malicious actors can acquire a wide array of credentials.
3. The agent obtains access on demand: The agent exchanges the user’s access token for one to access a resource with a short expiration date. The new token declares the delegation chain, so resources can know that an agent is accessing on behalf of a user, and adapt accordingly. And finally, if an agent gets compromised, the malicious actors will only gather short-lived access tokens, which will limit the damage they can cause.
The first two options are considered too risky and are discouraged. The third option, the token exchange one, follows security best practices and is recommended. The flow would be something like this:
We explored a similar scenario in Impersonation vs. Delegation: Getting On-Behalf-Of Tokens Right. That article covers a delegation scenario between a frontend and a backend that uses plain OAuth access tokens.
Step 1: In this scenario, the user uses a client to send a request to the agent. Notice how the client uses a JWT to authenticate against the agent.
GET /resource HTTP/1.1
Host: agent.example.com
Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJhdWQiOiJodHRwczovL2FnZW50LmV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTQ0MTkxMDA2MCwic2NvcGUiOiJhZ2VudCIsInN1YiI6InVzZXJAZXhhbXBsZS5uZXQifQ.OKaGU1r-dQktiPP75u65nanyuthauzS2gz8iIjwRmSCYLyDObGojAxbb7hrzJqfFv0a2lELaNsLEnNGWProzDg
You can explore the JWT’s payload in jwt.io. There, you can observe that the user is identified as user@example.net in the sub claim.
{
"aud": "https://agent.example.com",
"iss": "https://as.example.com",
"exp": 1441910060,
"scope": "agent",
"sub": "user@example.net"
}
Step 2: The agent needs to call a resource to fulfill this request. It will request an access token from the authorization server:
/codePOST /token HTTP/1.1
Host: as.example.com
…
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange
&resource=https%3A%2F%2Fbackend.example.com
&scope=”profile”
&subject_token=eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJhdWQiOiJodHRwczovL2FnZW50LmV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTQ0MTkxMDA2MCwic2NvcGUiOiJhZ2VudCIsInN1YiI6InVzZXJAZXhhbXBsZS5uZXQifQ.OKaGU1r-dQktiPP75u65nanyuthauzS2gz8iIjwRmSCYLyDObGojAxbb7hrzJqfFv0a2lELaNsLEnNGWProzDg
&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt
&actor_token=eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJhdWQiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTQ0MTkxMDA2MCwic2NvcGUiOiJ0b2tlbiIsInN1YiI6Imh0dHBzOi8vYWdlbnQuZXhhbXBsZS5jb20ifQ.EU5ypJHTH19eO2tI9M5vcpcQzMOmuK6qwWgoUp2MigKUMx4DWiNZH0j1hCLyqiF8LJrn2y5KG8aJZfgCZvPiCw
&actor_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt
Note how the agent uses its own JWT to authenticate against the access token in the Authorization header. The request body includes three key parameters:
- resource: The backend service the agent needs to access.
- scope: The scope the agent will access in the resource. Notice how it is different from the original user’s JWT.
- subject_token: Is the JWT the user sent in step 1.
- actor_token: The agent’s credentials (a JWT) to access the token API in the authorization server.
If the actor_token parameter is missing, the exchanged token won’t contain the delegation chain. It will be a case of impersonation, as the resource won’t be able to tell that there’s an AI agent acting as an intermediary.
Step 3: The authorization server will check the access policies. If this request aligns with the policies, it will return a new token that the agent will be able to use against the resource.
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store
{
"access_token":
"eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJhdWQiOiJodHRwczovL2JhY2tlbmQuZXhhbXBsZS5jb20iLCJpc3MiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiZXhwIjoxNDQxOTEzNjEwLCJzY29wZSI6InByb2ZpbGUiLCJzdWIiOiJ1c2VyQGV4YW1wbGUubmV0IiwiYWN0Ijp7InN1YiI6Imh0dHBzOi8vYWdlbnQuZXhhbXBsZS5jb20ifX0.cnXn7IlEcDUARZljUzGEs_8zVdRJDYUapTC8sxoVWXzotJNH9jy2YOa0SXu167jMsRnld4QiliJsXdLkPCZ2BQ",
"issued_token_type":"urn:ietf:params:oauth:token-type:jwt",
"token_type":"Bearer",
"expires_in":3600
}
The payload of the token is:
{
"aud":"https://backend.example.com",
"iss":"https://as.example.com",
"exp":1441913610,
"scope":"profile",
"sub":"user@example.net",
"act":
{
"sub":"https://agent.example.com"
}
}
Observe how the user is still the sub. However, the delegation chain in the act claim brings transparency into who is really acting on behalf of the user.
Also note how the audience (aud) of this new token is limited to the backend resource.
What we just described is the core flow for token exchange, and it is the basis for the next examples in this article.
Learn more about the differences between impersonation and delegation in Impersonation vs. Delegation: Getting On-Behalf-Of Tokens Right.
Access Policies With OAuth Token Exchange
A key moment in the token exchange flow is the step when the authorization server validates the exchange request against the authorization policies (step 3).
However, the token exchange RFC only provides general security guidelines on how authorization servers should perform this verification. Each server is free to implement this step as they see fit, which opens the door to some creative applications.
For example, an authorization server from a cloud provider will delegate this validation to the cloud’s IAM service.
Let’s see some options for this validation step.
The may_act Claim
The simplest way of validation is for the client’s token to explicitly grant the agent permission to to act on their behalf. This is done by including the may_act claim (in step 1).
In the following example, the token explicitly declares that the agent can act on their behalf:
{
"aud": "https://agent.example.com",
"iss": "https://ass.example.com",
"exp": 1441910060,
"scope": "agent",
"sub": "user@example.net"
"may_act": {
"sub": "https://agent.example.com"
}
}
As this token was issued and signed by the authorization server, the authorization server can trust this claim and skip further validation steps.
IAM Tools
IAM tools can act as authorization servers, or integrate with them, to apply access policies that can be enforced across all the organization’s infrastructure.
IAM tools can provide a single pane of glass to enforce policies like:
- Restrict scopes.
- Limit access depending on time of day.
- Throttle number of accesses.
- Provide one-time access.
As centralized tools, they also provide visibility into who is accessing what. This allows administrators to detect abnormal behavior and respond by blocking access.
Human Approval for AI Workloads
At the beginning, we mentioned that AI agents push identity infrastructures to the limit. Think about it, compared to traditional workloads:
- Agents perform general tasks that require access to a wider range of resources.
- Access to resources is not predictable, but rather intermittent and random.
This non-deterministic nature means that an AI agent might analyze a codebase and decide that the best way to optimize it is to destroy a database.
So, sometimes agentic workloads require a human-in-the-loop to validate access manually. Especially for risky operations or while tuning a new agent. Read more about human approval for agentic workloads in How AAuth Brings Human Approval Into AI Agent Authorization.
Token exchange is ideal in these scenarios, as the agent will receive the access token after human approval.
Using OAuth Token Exchange to Bridge Between Infrastructures – Workload Identity Federation
By creating a system of trust between two identity providers (IdPs), accounts on one side can access services on the other.
Think of a multi-cloud scenario where an agent on an EC2 instance wants to access a protected resource in Google Cloud. It’s possible to set up Workload Identity Federation so that:
- Identities in AWS map to identities on Google Cloud.
- An AI agent running on EC2 can use token exchange against Google Cloud to access a resource there.
Once everything is set up, the preferred method to obtain these cross-cloud credentials is via the cloud provider’s API.
However, it’s also possible to use OAuth token exchange. In summary, the flow is as follows:
First, the workload must obtain credentials from the external IdP. This step is usually performed using the GetCallerIdentity method in AWS’s API. From a Python client, this would look like:
request = AWSRequest(
method="POST",
url="https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15",
headers={
"Host": "sts.amazonaws.com",
"x-goog-cloud-target-resource": f"//iam.googleapis.com/projects/{project_number}/locations/global/workloadIdentityPools/{pool_id}/providers/{provider_id}",
},
)
SigV4Auth(boto3.Session().get_credentials(), "sts", "us-east-1").add_auth(request)
# Create token from signed request.
token = {"url": request.url, "method": request.method, "headers": []}
for key, value in request.headers.items():
token["headers"].append({"key": key, "value": value})
print("Token:\n%s" % json.dumps(token, indent=2, sort_keys=True))
print("URL encoded token:\n%s" % urllib.parse.quote(json.dumps(token)))
See how the x-goog-cloud-target-resource ties this token to the identity provider on the Google Cloud side.
The client can use this TOKEN in OAuth token exchange requests to obtain short-lived access tokens. A request to Google Cloud’s IdP would look like:
POST /v1/token HTTP/1.1
Host: sts.googleapis.com
Content-Type: application/json
…
{
"audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
"requestedTokenType": "urn:ietf:params:oauth:token-type:access_token",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"subjectTokenType": "urn:ietf:params:aws:token-type:aws4_request",
"subjectToken": TOKEN
}
Note how:
- GoogleCloud’s IdP supports receiving the request body in JSON format.
- The
audienceclaim matches thex-goog-cloud-target-resourceheader from earlier. - The requested
scopeis limited to theauth/cloud-platformAPI.
Token Exchange Enables Transparency on AI Workloads
As we mentioned earlier, agentic workloads often need to delegate to several agents while performing a task. Those agents may delegate to others, and so on, creating a delegation chain that muddies the flow and complicates troubleshooting.
It’s crucial to have visibility of this delegation chain when investigating security incidents to identify what link in the chain is compromised and properly assess which assets are affected.
Luckily, when using the delegation syntax in OAuth token exchange, the chain of delegation is represented in the generated tokens under the act field:
{
"aud":"https://service26.example.com",
"iss":"https://issuer.example.com",
"exp":1443904100,
"nbf":1443904000,
"sub":"user@example.net",
"act":
{
"sub":"https://service16.example.com",
"act":
{
"sub":"https://service77.example.com"
}
}
}
This is the payload for an access token. We can see that:
- This token authorizes against
service26. - The effective user is
user@example.netas per thesubclaim. - Two token exchanges have taken place, as per the
actclaim.- The first one was by
service77. - The second one was by
service16,which is now using this token.
- The first one was by
If service26 were to perform a new token exchange, the authorization server would expand the act claim in the new token:
{
"aud":"https://service42.example.com",
"iss":"https://issuer.example.com",
"exp":1443904100,
"nbf":1443904000,
"sub":"user@example.net",
"act":
{
"sub":"https://service26.example.com",
"act":
{
"sub":"https://service16.example.com",
"act":
{
"sub":"https://service77.example.com"
}
}
}
}
Why AI Needs OAuth Token Exchange
Agentic AI changes the scale of workload identity.
OAuth token exchange is designed to help developers navigate this paradigm and follow identity best practices without costly custom implementations.