developers

The Many Faces of OAuth 2.0 Token Exchange

A journey through the various facets of token exchange: from use cases to management complexity

Most developers face the need for token exchange when their architecture outgrows its initial assumptions. You have a service that holds a token for one purpose, and now it needs a different token for a different purpose. While simply forwarding the existing token or creating a fresh one might seem like intuitive solutions, both methods introduce significant issues. The OAuth 2.0 Token Exchange flow, as specified in RFC 8693, was explicitly created to address these challenges.

Token exchange gives you a standards-based mechanism for converting one security token into another. The concept is straightforward: a client sends a token to the authorization server and receives a different token back, scoped for a different context, audience, or purpose. But behind this simple operation lie several distinct scenarios, each with its own trade-offs and security implications.

Let's walk through the use cases that make token exchange essential in modern architectures, and see how they connect to solutions available today.

What Is Token Exchange

At its core, OAuth 2.0 Token Exchange is a protocol extension that turns the authorization server into a Security Token Service (STS), i.e., a service capable of validating security tokens provided to it and issuing new security tokens in response. The flow is straightforward: A client sends a subject_token, optionally accompanied by an actor_token, and receives a new token appropriate for the target context. The subject_token is the token representing the user or entity on whose behalf the request is being made; the actor_token identifies the party performing the exchange.

The specification defines two fundamental modes of operation:

  • Impersonation: The requesting party receives a token that makes it indistinguishable from the original subject. The downstream service has no way to tell whether the actual user or an impersonating party is making the call.
  • Delegation: The requesting party receives a token that preserves both identities. The downstream service knows who the user is and who is acting on their behalf, typically through an act (actor) claim embedded in the new token.

This distinction matters more than it might seem at first glance. Impersonation is powerful but opaque; delegation is more constrained but auditable. The choice between them shapes your security posture and your ability to trace what happened when something goes wrong.

Administrative Impersonation

Let's start by exploring the first intuitive use case: a customer reports that their dashboard shows incorrect data, but only for their account. Your support engineer needs to see exactly what the customer sees, with the same permissions and data access, to diagnose the issue.

This is the classic administrative impersonation scenario. An administrator exchanges their own token for one that represents the customer's identity. The resulting token carries the customer's sub claim, their scopes, and their audience. From the application's perspective, the request is coming from the customer.

The token exchange request in this case uses only a subject_token (identifying the user to impersonate) and provides no actor_token, because the intent is full impersonation. The authorization server validates that the requesting party has impersonation privileges and issues a token bound to the target user's identity.

There's a problem, though: because this is true impersonation, you lose the audit trail of who actually performed the actions. The downstream service cannot distinguish between the real user and the admin. This is why impersonation-based token exchange should be paired with external logging mechanisms that record who initiated the exchange, when, and for what reason. Without that, you're creating a security gap in the name of troubleshooting.

Managing Protocol Transition

Organizations rarely get to start from scratch. Legacy systems persist, and with them persist older protocols. A common scenario is transitioning from SAML-based authentication to OpenID Connect (OIDC) while keeping both systems operational during the migration.

Token exchange handles this gracefully. A service that authenticates users via SAML can exchange the SAML assertion for an OAuth 2.0 access token. The authorization server validates the incoming SAML artifact and issues a standard access token that downstream services understand.

The subject_token_type parameter in the exchange request identifies the format of the incoming token. RFC 8693 defines several token type identifiers, including urn:ietf:params:oauth:token-type:saml2 for SAML 2.0 assertions and urn:ietf:params:oauth:token-type:jwt for JWT tokens. This flexibility means the authorization server can accept tokens from different protocol families and normalize them into a consistent format for your modern services.

In practice, this pattern appears during company acquisitions and mergers where two organizations with different identity stacks need to interoperate before a full migration is complete. Rather than forcing all users to re-authenticate against a new system, token exchange bridges the gap: users authenticate as they always have, and the system translates their credentials into whatever the consuming service requires.

Chaining Service Calls

This is where token exchange becomes indispensable for most teams. You have a microservices architecture where Service A receives a user request, processes it, and needs to call Service B on the user's behalf. Service B might then need to call Service C.

The following diagram summarizes this scenario:

Chaining service calls diagram

The question is: what credentials does each service use when calling the next one?

There are three options, each with clear trade-offs.

Option 1: The service uses its own credentials

Service A calls Service B using its own client credentials, ignoring the user context entirely. This is the simplest approach and works well for service-to-service calls that don't need user context (batch processing, system health checks, data synchronization).

However, this option has a problem when a user is involved or your services need the full context. If Service A uses its own credentials, Service B cannot enforce user-level authorization. It doesn't know which user triggered the request, so it can't check whether that user should have access to the requested resource. You've lost the security context that makes accurate authorization possible.

Option 2: The service impersonates the user

Service A passes the user's original token directly to Service B, or exchanges it for a token that makes Service A indistinguishable from the user. Service B sees a request that appears to come from the user and applies user-level authorization.

In this case, the problem is that Service B cannot distinguish the user's direct actions from actions taken by Service A on the user's behalf. If Service A is compromised, it can make any call the user is authorized to make with no way for downstream services to apply different trust levels to direct vs. proxied requests. You've preserved the user context but lost the service context.

Option 3: The service acts on behalf of the user (Delegation)

Service A exchanges the user's token for a new token that identifies both the user (as subject) and Service A (as actor). The resulting token carries an act claim that tells Service B: "This request is about User X, performed by Service A."

This is the delegation model, and it's the one RFC 8693 was primarily designed to support. Service B can now make nuanced authorization decisions: "User X can read this data, and Service A is authorized to act on User X's behalf, so this request is allowed." If Service A tries to access something the user hasn't authorized it to do, the request fails.

The act claim is nestable, meaning that if Service B then calls Service C on behalf of the user, the delegation chain grows: Service C sees that Service B is acting on behalf of User X with Service A as the original actor. This chain provides a complete audit trail of how the request propagated through the system.

The trade-off here is complexity. Each hop requires a token exchange, which adds latency and requires each service to be registered as a client with the authorization server. But for architectures where security and auditability matter (and they always should), delegation is the principled choice.

Token Exchange and Federated Identity

The chain-of-calls scenario becomes significantly more complex when services span security domains, for example, when services are provided by third-party organizations. Consider the situation shown by the following diagram:

Cross-domain chaining service calls diagram

  • Service A has a token to access Service B on behalf of a user in MyCompany’s security domain.
  • Service B needs to call Service C, which is protected in the External Provider’s domain.
  • Service B needs an access token valid in MyCompany’s domain to access Service C on behalf of the user.

This is the federated identity challenge. The token that Service B received from MyCompany's authorization server means nothing to the External Provider’s domain. A token issued by one authorization server is not automatically accepted by another, and for good reason: trust boundaries exist to limit blast radius.

In a standard token exchange, Service B would present its token to the External Provider's authorization server and request a new token. But the External Provider's authorization server has no reason to trust a token issued by MyCompany unless a trust relationship has been explicitly established.

Solving this requires federation at the authorization server level. External Provider's authorization server must be configured to accept tokens from MyCompany domain as valid subject tokens in an exchange request. This involves pre-established trust (through metadata exchange, certificate validation, or direct configuration) and mapping between the identity representations in each domain.

In practice, this is where token exchange alone starts showing its limitations. Each cross-domain hop requires explicit trust configuration. As the number of domains grows (enterprise integrations, SaaS ecosystems, multi-cloud deployments), the matrix of bilateral trust relationships becomes unmanageable.

Cross App Access and Identity Chaining

This scaling problem is precisely what Cross App Access (XAA) aims to solve. XAA implements the Identity Assertion JWT Authorization Grant, an OAuth extension that introduces a mediator into the cross-domain exchange: the enterprise identity provider (IdP).

The key insight is that the IdP already knows about both applications and the user's relationship to each. Rather than requiring every pair of domains to establish bilateral trust, XAA centralizes access decisions in the IdP. Let's see how this works concretely.

The flow involves four parties:

  • Requesting App: The application (or AI agent) in MyCompany domain that needs to access a resource in another domain (External Provider).
  • Enterprise IdP: The identity provider in MyCompany domain that authenticates employees and manages cross-app policies.
  • Resource App: The application that owns the protected API in the External Provider domain.
  • Resource Authorization Server: The authorization server that issues access tokens for the Resource App's protected API in the External Provider domain.

The following diagram shows how the exchange unfolds:

Cross App Access - XAA authorization flow

Here is the description of each step:

  1. The employee logs in to the Requesting App via SSO and obtains an ID token from the IdP.
  2. The Requesting App sends that ID token back to the IdP, asking for a cross-domain identity assertion (an ID-JAG, a special JWT scoped for cross-app use).
  3. The IdP checks its XAA policy: is this Requesting App allowed to access the Resource App on behalf of this user? If yes, it returns the ID-JAG.
  4. The Requesting App presents the ID-JAG to the Resource App's authorization server.
  5. The Resource App's authorization server validates the ID-JAG (using the IdP's public keys via OIDC discovery) and issues an access token.
  6. The Requesting App calls the Resource App's API with that access token.

Notice the critical difference from plain token exchange: in step 3, the IdP enforces a policy decision. An administrator explicitly configures which apps can reach which resources, giving IT visibility and control over cross-app data sharing. Users don't face repetitive consent flows, and the organization maintains a centralized view of who can access what.

Identity Chaining is the broader term for this pattern: the user's identity assertion flows from the original authentication through every downstream service in a standardized way, without requiring ad-hoc trust configuration at each boundary. XAA is one concrete implementation of identity chaining, built on OAuth primitives.

This approach is particularly relevant in scenarios involving AI agents, where a single user request might trigger calls to services across multiple third-party providers, each with their own security domain.

Token Exchange and Auth0

Auth0 implements token exchange through mechanisms that address different points in the spectrum we've discussed.

Custom Token Exchange

Custom Token Exchange implements RFC 8693 on Auth0's /oauth/token endpoint with full developer control over the validation logic. You define a Token Exchange Profile that maps a subject_token_type URI to a custom Action. When a token exchange request arrives, Auth0 invokes your Action code to validate the incoming token, enforce authorization rules, and associate it with a user in your tenant.

This is the mechanism for protocol transitions and custom federation scenarios. Auth0 treats the subject_token as an opaque string, meaning you can accept any token format: JWTs from another identity provider, SAML assertions from a legacy system, or proprietary tokens from a partner's API. Your Action code owns the validation logic, and Auth0 handles the issuance of a standards-compliant token on the other side.

Check out this article for practical introduction to Custom Token Exchange in Auth0.

Token Vault

For the AI agent scenario, where services need to call third-party APIs on behalf of users across multiple providers, Auth0 offers Token Vault. This builds on token exchange but adds secure storage and automatic lifecycle management for third-party tokens.

The flow works like this: a user authenticates and connects their accounts (Google, GitHub, Slack, Microsoft, and others). Token Vault stores the resulting tokens securely and handles refresh automatically. When an AI agent needs to call a third-party API on behalf of that user, it performs a token exchange to retrieve a valid access token from the vault.

The resulting token includes an act claim that identifies the AI agent, creating an audit trail of which agent accessed which service on behalf of which user. This is critical for enterprise deployments where compliance requires knowing not just what happened, but what automation triggered it.

Take a look at this article for a quick introduction to Token Vault.

On-Behalf-Of Token Exchange

For the service chain scenario, Auth0's On-Behalf-Of (OBO) token exchange implements the delegation pattern directly. A middle-tier service exchanges the incoming user token for a new token scoped to the downstream API, preserving the user's identity while adding itself to the delegation chain via the act claim.

Auth0 supports up to five levels of delegation chain nesting, providing visibility into the full path a request takes through your architecture. Each token in the chain carries the sub claim (maintaining user identity), the aud claim (scoped to the target service), and the nested act claim (recording the chain of services involved).

Cross App Access

For the federated identity scenario, where a requesting application needs to call a resource API protected by a different authorization server, Auth0 supports Cross App Access (XAA). This feature implements the Identity Assertion Authorization Grant OAuth extension, enabling applications and AI agents to obtain tokens for cross-domain API calls without requiring users to go through repetitive consent flows.

The mechanism works through Auth0 acting as the Resource Authorization Server. When the Requesting App needs to reach a Resource App registered with Auth0, it sends the user's ID token to its IdP, such as Okta, and receives an ID-JAG (Identity Assertion JWT) in return. The IdP issues this assertion only if an administrator has configured the cross-app connection in the Admin Console. The Requesting App then presents the ID-JAG to the Resource App's authorization server, i.e., Auth0, which validates it via OIDC discovery and issues a scoped access token.

This gives IT centralized visibility into cross-app data sharing. Administrators define which applications can reach which resources, and Auth0 enforces those policies at exchange time.

Choosing the Right Approach

Token exchange is not a single solution but a family of patterns. The right choice depends on what context you need to preserve and what trust boundaries you need to cross:

  • Administrative impersonation when troubleshooting requires seeing exactly what the user sees
  • Protocol transition when bridging legacy and modern identity systems during migrations
  • Delegation when service chains need user context with full auditability
  • Cross App Access / Identity Chaining when delegation spans multiple security domains
  • Token Vault when AI agents need managed access to third-party APIs on behalf of users

The spread of AI agents has made token exchange more relevant than ever. An AI agent that orchestrates actions across multiple services and multiple providers on a user's behalf is, fundamentally, a delegation chain. The mechanisms we've discussed provide the security foundation for making that chain auditable, authorized, and scoped to what the user actually intended.

The token exchange specification gives us the vocabulary. Implementations like Auth0's give us the tools. The choice of which pattern to apply at each boundary is yours.

About the author

Andrea Chiarelli

Andrea Chiarelli

Principal Developer Advocate

I have over 20 years of experience as a software engineer and technical author. Throughout my career, I've used several programming languages and technologies for the projects I was involved in, ranging from C# to JavaScript, ASP.NET to Node.js, Angular to React, SOAP to REST APIs, etc.

In the last few years, I've been focusing on simplifying the developer experience with Identity and related topics, especially in the .NET ecosystem.

View profile