Implement on-behalf-of token exchange for multi-tenant agents with Amazon Bedrock AgentCore Gateway Amazon Bedrock AgentCore Gateway now supports OAuth 2.0 Token Exchange (RFC 8693) for multi-tenant agents, enabling on-behalf-of token exchange that preserves user identity across downstream API calls while preventing the confused deputy problem. The feature allows agents to obtain audience-bound tokens for each tenant, ensuring cryptographic least privilege and eliminating the need for agent-side exchange logic. Artificial Intelligence https://aws.amazon.com/blogs/machine-learning/ Implement on-behalf-of token exchange for multi-tenant agents with Amazon Bedrock AgentCore Gateway When you deploy generative AI agents into multi-tenant production architectures, you face a specific identity problem: when an agent calls a downstream API on behalf of a user, whose identity travels with the call? Running the call as the agent’s service identity collapses the audit trail, because every downstream system must trust the agent unconditionally. Forwarding the user’s token unchanged turns every downstream tool into a confused deputy. Neither option scales when one agent fronts many tenants and the user is not present at the moment of the tool call. The OAuth 2.0 Token Exchange specification RFC 8693 addresses this exact problem, and Amazon Bedrock AgentCore Identity supports it natively as a credential-provider grant type. Building multi-tenant agents with Amazon Bedrock AgentCore https://aws.amazon.com/blogs/machine-learning/building-multi-tenant-agents-with-amazon-bedrock-agentcore/ and Apply fine-grained access control with Bedrock AgentCore Gateway interceptors https://aws.amazon.com/blogs/machine-learning/apply-fine-grained-access-control-with-bedrock-agentcore-gateway-interceptors/ establish the conceptual foundation for on-behalf-of OBO token exchange in agentic systems. This post is the implementation guide. It walks through a complete multi-tenant OBO setup against Okta, shows the JSON Web Token JWT claim transformations on each hop, and demonstrates how audience binding produces defense in depth that scales across tenants. The reference implementation, TravelBot , is a multi-tenant booking assistant that serves two example tenants Acme and Globex . The reference implementation for this post will be available in the aws-samples/sample-obo-flow-poc https://github.com/aws-samples/sample-obo-flow-poc repository after publication. The OBO pattern is essential whenever an agent fronts multiple downstream services or tenants and the inbound token’s audience differs from any single downstream API. For a single-tenant agent where the inbound audience already matches the downstream service, direct token forwarding can be sufficient. The rest of this post focuses on the multi-tenant case. Introducing on-behalf-of token exchange in AgentCore Identity Amazon Bedrock AgentCore Identity supports OAuth 2.0 Token Exchange RFC 8693 as a native credential-provider grant type. With this capability, AgentCore Gateway can transparently exchange an inbound user token for a new, audience-bound token before invoking a downstream tool, without requiring the agent itself to implement the exchange. This capability provides the following benefits: Identity propagation across tenant boundaries – The original caller’s identity is preserved end to end through the sub claim, even as the audience changes per tenant. Cryptographic least privilege – Each downstream call carries a token bound to one downstream service through the aud claim. A token issued for one tenant can’t be used at another. No agent-side exchange logic – The agent code obtains a single inbound token and invokes tools. AgentCore performs every exchange. Standards-based – The implementation is built on the RFC 8693 token-exchange grant. Authorization servers that support the same grant type with a compatible request shape can serve as a credential provider. The confused deputy problem in agentic AI Consider an agent that handles requests like “show me my bookings” for users from many different tenants. Three implementation choices are available, and only one of them is correct. Service-account impersonation – The agent authenticates as itself and asserts the user’s identity in a request header or path parameter. Every downstream API must trust the agent unconditionally. A compromised agent can act as any user against any tenant. This is the textbook confused deputy. Direct user-token forwarding – The agent reuses the inbound user token to call downstream APIs. This works only when the inbound token’s audience already matches the downstream API. That condition is rarely true in multi-tenant systems and never true when the agent fronts a tool gateway. On-behalf-of token exchange – The agent’s authorization broker exchanges the inbound subject token for a new token whose sub is the original caller, whose aud is the downstream API, and whose signature comes from an authorization server the downstream API trusts. The result is a token cryptographically scoped to a single downstream call on behalf of a single user. OBO is the only choice that preserves the user’s identity end to end, enforces least privilege at the audience boundary, and produces a token the downstream API can validate independently without trusting the agent. Implementing RFC 8693 requires alignment across the agent runtime, the authorization servers, and the downstream APIs. When any one of them is misconfigured, the security posture degrades silently. Amazon Bedrock AgentCore Gateway and AgentCore Identity remove that coordination burden. The Gateway intercepts the tool call, identifies the target tenant, and instructs Identity to perform the exchange against the tenant’s authorization server before the downstream call is issued. Impersonation compared to on-behalf-of In an OBO exchange, the inbound token’s sub claim is preserved while the aud claim is rewritten to the downstream service. The actor of the exchange is recorded in a separate claim act per RFC 8693, cid in Okta , so the downstream API can answer two questions from a single token: who is being acted on behalf of? the sub claim and who is performing the action? the actor claim . Authorization decisions belong on sub . Audit logs and rate-limiting decisions belong on the actor. For a deeper conceptual treatment of impersonation versus on-behalf-of, refer to Apply fine-grained access control with Bedrock AgentCore Gateway interceptors https://aws.amazon.com/blogs/machine-learning/apply-fine-grained-access-control-with-bedrock-agentcore-gateway-interceptors/ . The following diagram contrasts the two patterns using TravelBot’s booking tools. In direct user-token forwarding, the inbound and downstream tokens are the same token. In OBO, each hop carries a distinct token bound to a different tenant’s booking API. Figure 1. Direct user-token forwarding vs. on-behalf-of token flow for TravelBot’s booking tools. In the direct-forwarding pattern top , the agent forwards the user’s token unchanged. The token’s aud claim was issued for the agent’s API, so the downstream tools must either skip audience validation or accept whatever audience the upstream service hands them. Both options reintroduce the confused deputy. In the on-behalf-of pattern bottom , AgentCore exchanges the token at each hop for a new token whose aud claim is bound to a single downstream service and whose scp is reduced to the minimum required. The sub claim is preserved across hops, so audit and authorization decisions still resolve to the original user, while the actor claim records AgentCore as the delegate that performed the exchange. Solution overview Throughout this post, the following identifiers refer to specific Okta resources in the TravelBot reference. Identifier | Role | TravelBot Provider | Okta authorization server that issues the inbound JWT to the agent. | ACME Travel API | Okta authorization server that mints OBO tokens for the Acme tenant. | Globex Travel API | Okta authorization server that mints OBO tokens for the Globex tenant. | TravelBot Agent Client | Okta API Services app for the agent’s machine-to-machine path legacy/testing . | TravelBot User Client | Okta OpenID Connect OIDC app for the 3-legged user login. | AgentCore Delegate | Okta app whose credentials AgentCore Identity uses to perform the token exchange. | The TravelBot reference implementation uses Okta on both sides of the exchange. One Okta authorization server TravelBot Provider authenticates the agent. Two more ACME Travel API and Globex Travel API mint OBO tokens for each tenant. All three are Okta Custom authorization servers. Okta’s built-in Org authorization server does not support custom audiences or scopes and cannot serve this pattern. The same roles can be played by other identity providers IdPs with equivalent capabilities. Because RFC 8693 is implemented at the authorization-server layer, the same architecture should work with other authorization servers that support the token-exchange grant. The exact request shape AgentCore Identity sends subject token type , audience, actor-token presence, and client authentication method is set per credential provider through the customParameters map, so adapting to a different IdP is a configuration change, not a code change. We recommend that you verify against your specific deployment. Auth0 through its Custom Token Exchange feature and Keycloak with its token-exchange feature enabled per realm advertise compatible RFC 8693 support and work with grantType: TOKEN EXCHANGE . AWS IAM Identity Center supports a conceptually similar pattern through its Trusted Token Issuer feature with a different request and response shape than RFC 8693. This differs from Okta’s Trusted Servers relationship described earlier. Microsoft Entra ID’s on-behalf-of flow uses grant type=urn:ietf:params:oauth:grant-type:jwt-bearer with requested token use=on behalf of RFC 7523 rather than the RFC 8693 grant type; AgentCore Identity supports this flow natively through grantType: JWT AUTHORIZATION GRANT on the credential provider. Amazon Cognito user pools can serve as the provider IdP that authenticates the inbound agent call. Confirm the current grant-type support against the AgentCore Identity documentation if you plan to use Cognito for the consumer-side OBO role. AgentCore Identity supports both public and virtual private cloud VPC private connectivity to tenant authorization servers, including identity providers hosted inside your VPC. See Connect to private identity providers https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-private-idp.html in the AgentCore developer guide for configuration patterns. The architecture has six components: Provider authorization server – Issues the inbound JWT that the agent presents to the Gateway. In TravelBot this is the Okta authorization server named TravelBot Provider . AgentCore Gateway – Validates the inbound JWT against the provider’s JSON Web Key Set JWKS , routes the tool invocation to the correct tenant target, and orchestrates the OBO exchange. AgentCore Identity – Holds the delegate client credentials and executes the RFC 8693 token exchange against the tenant authorization server. Per-tenant authorization servers – Mint OBO tokens scoped to each tenant’s audience. In TravelBot, ACME Travel API and Globex Travel API are two distinct Okta authorization servers, each with its own audience and access policy. Per-tenant API surface – An Amazon API Gateway HTTP API with one JWT authorizer per tenant. Each authorizer validates issuer, audience, and required scopes, providing the last line of defense against cross-tenant token reuse. Tenant business logic – An AWS Lambda function that receives the validated OBO token and reads the claims to make tenant-specific decisions. It enforces write permission from the per-user authorized scopes claim and stores bookings in Amazon DynamoDB partitioned by the sub claim, so a user can only ever read their own records. The rest of this post walks through these components in the order a request traverses them. The following diagram summarizes the request flow: Figure 2. End-to-end OBO request flow with AgentCore Gateway. The workflow consists of the following steps: - A user authenticates at the provider authorization server through the three-legged authorization code login, and the agent receives the inbound JWT, audience-bound to the Gateway. - The agent invokes a tool through Model Context Protocol MCP , presenting the inbound JWT as a bearer token. - The Gateway retrieves the provider’s JWKS, validates the JWT signature, and confirms the audience matches travelbot-provider . - The Gateway selects the credential provider associated with the Acme target and asks Identity for an OBO token. - Identity sends an RFC 8693 token-exchange request to Acme’s authorization server. The subject token is the inbound JWT, and the requested audience is https://api.acme-travel.example . - Okta verifies that Acme’s authorization server trusts the provider issuer, applies the access policy on the delegate client, computes the per-user authorized scopes claim, and signs the OBO JWT. - Identity returns the OBO JWT to the Gateway. - The Gateway calls the /acme route on API Gateway, presenting the OBO JWT as the bearer token. - The API Gateway JWT authorizer validates issuer, audience, and required scopes before forwarding the request to the AWS Lambda function. - The Lambda function decodes the OBO claims, enforces write permission from authorized scopes , queries DynamoDB partitioned by the user’s sub , and returns the tenant-specific response. - The response flows back through API Gateway and AgentCore Gateway to the agent. The three-legged inbound login Because TravelBot authenticates a real user, the agent runs an OAuth 2.0 authorization code flow before it talks to the Gateway: - The agent generates a random state value and builds an Okta /authorize URL with response type=code , scope=openid email gateway/invoke , and a redirect uri pointing at a local callback listener. - The agent starts the callback listener, then opens the browser. - The user authenticates at Okta. - Okta redirects to the callback with an authorization code and the state . - The callback listener verifies state to defend against cross-site request forgery CSRF , and exchanges the code for the inbound access token at Okta’s /v1/token endpoint. - The agent uses that access token whose sub is the authenticated user as the bearer token for every Gateway call. In a production web application, the local callback listener is replaced by a normal route on the application’s backend behind a load balancer, and the redirect uri becomes that public HTTPS endpoint. The exchange logic is identical. Only where the redirect lands changes. Token transformation deep dive The following table summarizes how each JWT claim is transformed between the inbound token and the OBO token. The sub claim is preserved end to end. Everything else is rewritten or added by the tenant authorization server. Claim | Phase 1 inbound | Phase 3 OBO | Change | iss | Provider authorization server | Tenant authorization server | Rewritten | aud | travelbot-provider | https://api.acme-travel.example | Rewritten | sub | alice@acme-travel.example | alice@acme-travel.example | Preserved | cid | Provider client TravelBot Agent Client | Delegate client AgentCore Delegate | Rewritten actor | scp | openid email gateway/invoke | booking/read booking/write | Rewritten | authorized scopes | — | booking/read | New | Three claims carry the security story: - The sub claim is preserved end to end, so audit logs and authorization decisions resolve to the original caller. - The aud claim is rewritten to the tenant API, binding the token cryptographically to a single downstream service. - The cid claim records the delegate that performed the exchange, separating the actor from the caller. A note on claim naming for cross-IdP environments: Okta emits scopes as the scp array claim and the actor as cid . RFC 8693 and OAuth 2.0 use scope a space-delimited string and act a nested object . When normalizing audit logs across multiple IdPs, account for both shapes rather than assuming the spec form. In the TravelBot reference, the inbound token is obtained through the three-legged authorization code login, so the sub claim is the authenticated human user. In a machine-to-machine flow client credentials grant , sub would instead be the caller application’s client ID. Phase 1 — Inbound token, issued by the provider authorization server: The agent obtains this token through the three-legged authorization code login. It is bound to the Gateway’s expected audience travelbot-provider and carries a single coarse scope, gateway/invoke , which authorizes calling the Gateway and nothing further. Phase 2 — RFC 8693 token exchange request, sent by AgentCore Identity to the Acme tenant authorization server: POST /oauth2/aus