OBO token exchange for multi‑tenant AI agents with Amazon Bedrock AgentCore Gateway

Implement on‑behalf‑of token exchange for multi‑tenant agents with Amazon Bedrock AgentCore Gateway

This practical guide is for architects and engineering leaders building multi‑tenant generative‑AI agents that need per‑user, per‑tenant authorization. It shows why naive approaches fail, how the OAuth 2.0 on‑behalf‑of (OBO) pattern (RFC 8693) solves the issue, and how Amazon Bedrock’s AgentCore Gateway + AgentCore Identity implement a production‑ready exchange flow.

That moment, when a valid user intent collides with a token scoped for the wrong audience, is a common failure mode in multi‑tenant agent deployments.

“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?”

, Dhawalkumar Patel, Aswin Vasudevan, and Sahil Thapar, AWS

Why token passthrough and impersonation fall short

  • Token passthrough (forwarding the user’s inbound token) works only when the inbound token’s aud already matches the downstream API and the API trusts the inbound issuer. In heterogeneous, multi‑tenant setups that is uncommon.
  • Service‑account impersonation (agent uses its own credentials) collapses per‑user audit and creates a confused‑deputy risk. The agent’s delegated power can be misused to act for many users or tenants.
  • OBO (RFC 8693) is the pragmatic choice when you need the downstream resource to validate the caller independently while preserving the original user identity. OBO can preserve sub (the user principal), mint a tenant‑bound aud for the downstream service, and include the actor/delegate identity (cid/act). Note: exact claim names and whether sub is preserved depend on IdP behavior, verify your IdP’s exchange semantics.

How AgentCore Gateway + AgentCore Identity apply OBO

AgentCore Gateway validates inbound JWTs, routes tool invocations to tenant targets, and delegates outbound token issuance to AgentCore Identity. AgentCore Identity stores delegate credentials (one per tenant in the recommended pattern) and performs RFC 8693 token exchanges against tenant authorization servers so the agent code never has to implement exchange logic itself.

  1. An end user signs into the provider (e.g., TravelBot Provider) and receives an inbound JWT: [email protected], aud=travelbot-provider.
  2. The Gateway validates the inbound JWT (CUSTOM_JWT authorizer) and selects the tenant target (Acme or Globex) based on routing rules.
  3. AgentCore Identity exchanges the inbound JWT for an OBO token via RFC 8693. The issued token typically preserves sub, sets aud to the tenant API, and includes the delegate identity in cid/act.
  4. The Gateway calls the tenant API with the OBO token. The API enforces fine‑grained permissions (for example, authorized_scopes) and partitions data by tenant+subject.

Security caveats and protocol nuance (read before you trust revocation)

RFC 8693 standardizes the token‑exchange protocol but leaves policy and some behavior to the authorization server. Two operational caveats matter most:

  • Revocation and token linkage: RFC 8693 does not guarantee that revoking an inbound token will automatically invalidate exchanged OBO tokens. Linkage and revocation propagation are implementation‑specific (see RFC 8693 §2.1.6). If your security model requires immediate invalidation, design for short token lifetimes, explicit revocation hooks, or centralized introspection.
  • Client authentication and proof‑of‑possession: The spec allows multiple client authentication methods (client_secret_post, private_key_jwt, mTLS) and holder‑of‑key mechanisms like DPoP. If AgentCore or other agent infrastructure cannot present a DPoP/PoP key, you may need to disable DPoP for the exchange in that implementation. Disabling DPoP increases replay risk. Mitigate that with short TTLs, stronger client authentication for delegate credentials, and monitoring. Prefer private_key_jwt or mTLS for delegate clients when your threat model demands higher assurance.

Concrete strings and an example RFC 8693 exchange

Key RFC parameter:

  • grant_type=urn:ietf:params:oauth:grant-type:token-exchange

Okta (as used in the TravelBot reference) requires the subject_token_type for the exchange to be an access token rather than the default jwt. That Okta‑specific requirement must be configured in your Gateway target:

  • subject_token_type=urn:ietf:params:oauth:token-type:access_token (Okta requirement in the sample)

An exchange is a form‑encoded POST. Include the appropriate content type and follow your IdP’s client authentication requirement, for example the Authorization header with Basic or client_secret_post form fields depending on the IdP and client configuration:

Headers: Content-Type: application/x-www-form-urlencoded

Form body (example against an Okta tenant authorization server):

POST /oauth2/aus<acme-id>/v1/token

grant_type=urn:ietf:params:oauth:grant-type:token-exchange

&subject_token=<inbound JWT>

&subject_token_type=urn:ietf:params:oauth:token-type:access_token

&audience=https://api.acme-travel.example

&scope=booking/read+booking/write

&client_id=<delegate-client-id>

&client_secret=<delegate-client-secret>

Note: some IdPs require client authentication via the Authorization header (Basic) or support stronger methods like private_key_jwt or mTLS. The TravelBot sample uses CLIENT_SECRET_POST for convenience. For production, use private_key_jwt or mTLS when feasible.

Example token claim shapes (TravelBot reference)

Example inbound JWT (Phase 1) claims used in the sample:

  • iss: https://example.okta.com/oauth2/aus<provider-id>
  • aud: travelbot-provider
  • sub: [email protected]
  • cid: 0oa<provider-client-id>
  • scp: [“openid”, “email”, “gateway/invoke”]
  • exp: 1748395200

Example OBO token (Phase 3) claims minted by the tenant authorization server in the sample:

  • iss: https://example.okta.com/oauth2/aus<acme-id>
  • aud: https://api.acme-travel.example
  • sub: [email protected]
  • cid: 0oa<delegate-client-id>
  • scp: [“booking/read”, “booking/write”]
  • authorized_scopes: “booking/read”
  • exp: 1748395800

Important: IdPs differ. Some preserve sub exactly, others mint a new subject or format it differently. Confirm your IdP’s mapping and the actor/client claim name (cid vs client_id vs act) before relying on any particular claim for authorization or data partitioning.

Practical setup with bedrock-agentcore-control (Boto3)

The TravelBot reference performs three high‑level steps via the bedrock-agentcore-control API:

  1. Create a Gateway configured with a JWT‑validating authorizer (CUSTOM_JWT). The authorizer’s discoveryUrl points at the provider issuer and allowedAudience = provider_audience.
  2. Create one OAuth2 credential provider per tenant. Each provider configures discoveryUrl or authorizationServerMetadata and stores the delegate client credentials that AgentCore Identity will use for exchanges.
  3. Create one Gateway target per tenant. Each target includes customParameters such as audience and subject_token_type so AgentCore Identity knows how to call that tenant’s token endpoint.

Reference implementation: TravelBot (multi‑tenant booking assistant for Acme and Globex). The sample repository is published at https://github.com/aws-samples/sample-obo-flow-poc, check the repo for JSON examples and scripts that implement the steps above.

Okta‑specific behaviors and gotchas (sample‑based)

  • IdP treats exchanges as M2M: Okta (as used in the TravelBot reference) processes RFC 8693 exchanges as machine‑to‑machine and by default does not filter scp by user groups during the exchange. The reference workaround is to emit a computed Expression claim (authorized_scopes) that evaluates the user’s effective permissions at mint time. Rely on that claim in the resource server for authorization.
  • Client identity claim: Okta emits the delegate client identity in cid rather than client_id. Plan for cid/act mapping in downstream authorization logic.
  • subject_token_type: In the sample, Okta requires the exchange request to include subject_token_type=urn:ietf:params:oauth:token-type:access_token. AgentCore defaults to jwt for subject_token_type, so override this setting in the Gateway target for Okta tenants.
  • Trusted Servers: Each tenant authorization server must trust the provider issuer (Trusted Servers relationship in Okta) for the exchange to succeed in the sample configuration.
  • DPoP: The sample disables DPoP on provider and delegate Okta apps because AgentCore Identity does not present a client‑held DPoP key during exchanges. If you disable DPoP, mitigate the increased replay risk with short OBO TTLs, strong client auth for the delegate, and monitoring.
  • Delegate grant types: Ensure the AgentCore Delegate app lists urn:ietf:params:oauth:grant-type:token-exchange in allowed grant types and is properly assigned in the tenant authorization server’s access policy.

These Okta behaviors reflect the TravelBot sample and authors’ configuration. Validate precisely against your IdP documentation before you deploy.

Data partitioning and privacy note

TravelBot’s DynamoDB pattern partitions data by tenant and subject:

Warning: embedding PII (like email) in primary keys has privacy and compliance implications. Prefer tenant‑scoped user IDs or hash/anonymize sub values if your policy forbids raw PII in storage keys.

Operational checklist and recommendations

  • Per‑tenant credential providers: Use one OAuth2 credential provider per tenant to keep revocation, rotation, and least privilege boundaries clear.
  • Secret management: Store delegate client secrets in AWS Secrets Manager and automate rotation. Treat these secrets as highly sensitive.
  • Token lifetime: Issue short‑lived OBO tokens to reduce replay exposure, especially if DPoP is disabled.
  • Per‑user entitlements: Push per‑user entitlement evaluation into the IdP (Expression claim like authorized_scopes) so the resource server can trust the token’s claims without re‑evaluating groups on every request.
  • Monitoring & alerts: Track token‑exchange metrics and alert on spikes in invalid_request, unauthorized_client, audience_mismatch, and user_claim_evaluation_failure errors. These often indicate misconfiguration or compromise.
  • Rate limits and latency: Token exchange adds an extra roundtrip. Measure latency, budget IdP rate limits, consider safe short‑lived caching of OBO tokens for the duration of a user transaction, and batch calls where feasible.
  • Incident playbook: Document steps to revoke a compromised delegate: revoke client credentials at tenant IdPs, rotate AgentCore Identity config, invalidate cached tokens if possible, and reconfigure Gateway targets programmatically.

IdP compatibility checklist

  • Confirm the IdP supports RFC 8693 and the exact subject_token_type string it expects.
  • Confirm how the IdP represents the actor/client (cid vs client_id vs act) and the scope format (scp array vs space‑delimited string).
  • Confirm whether the IdP can evaluate per‑user claims at exchange time (Expression claims or equivalent) so you can embed authorized_scopes into the OBO token.
  • Confirm DPoP and holder‑of‑key behaviors and whether your agent infrastructure can present required keys.
  • Confirm token‑exchange endpoint rate limits and supported client authentication methods (client_secret_post, private_key_jwt, mTLS).

Performance, caching, and scale

Every tenant‑bound call that requires a fresh OBO token implies an IdP roundtrip unless you cache the OBO token. That increases latency and IdP load. Consider these mitigations:

  • Reuse short‑lived OBO tokens during a single user interaction rather than exchanging for every downstream request.
  • Gracefully back off when the IdP rate limits exchanges and surface clear errors to users when the exchange fails.
  • Verify whether AgentCore Identity caches OBO tokens in your runtime version, do not assume caching semantics. Design for the worst case until verified.

Key questions (and short, honest answers)

  • Why not just forward the user’s token?
    Forwarding works only when the inbound token’s aud matches the downstream API and the API trusts the inbound issuer. In multi‑tenant agents that call many distinct tenant APIs, that is rarely true.
  • Can the agent impersonate the user instead?
    Impersonation flattens per‑user audit and increases confused‑deputy risk. It is acceptable when downstream APIs do not require per‑user identity, but not when you need fine‑grained, aud‑bound authorization across tenants.
  • What exact grant do I use for the exchange?
    Use grant_type=urn:ietf:params:oauth:grant-type:token-exchange as defined by RFC 8693. Some IdPs (for example, the Okta sample) also require subject_token_type=urn:ietf:params:oauth:token-type:access_token.
  • How do I preserve per‑user scopes when the IdP treats exchanges as M2M?
    Have the IdP evaluate per‑user entitlements at mint time and emit a computed claim (for example, authorized_scopes). Authorize against that claim in the resource server rather than relying solely on exchanged scp lists.
  • Is disabling DPoP safe?
    Disabling DPoP removes proof‑of‑possession protections and increases replay risk. If you must disable it because agent infrastructure cannot present a PoP key, mitigate with short OBO TTLs, stricter client authentication for delegate credentials (prefer private_key_jwt or mTLS), and aggressive monitoring and rotation.
  • Where can I find a concrete sample to copy?
    The TravelBot reference implementation demonstrates these patterns; the sample repository is available at https://github.com/aws-samples/sample-obo-flow-poc. Verify the repo and the current AgentCore Boto3 API surface before copying snippets into production.

Three immediate next steps for leaders

  • Ask your IdP: Do you support RFC 8693 token exchange, what subject_token_type do you expect, how are actor and subject claims represented, and can you evaluate per‑user claims at exchange time?
  • Lock down delegate credentials: Assign one delegate client per tenant, store credentials in Secrets Manager, require strong client auth (private_key_jwt/mTLS when possible), and automate rotation.
  • Prototype with monitoring: Build a short‑lived OBO prototype, instrument token‑exchange metrics (invalid_request, unauthorized_client, audience_mismatch, user_claim_evaluation_failure), and set alerts before broad rollout.

“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.”

, Dhawalkumar Patel, Aswin Vasudevan, and Sahil Thapar, AWS

That quote captures the security goal behind using RFC 8693 exchanges in multi‑tenant agent architectures. Practically, OBO is the recommended pattern when downstream services must independently validate the caller and preserve per‑user identity across tenants. But remember RFC 8693’s caveats: validate IdP behavior for sub and actor claims, confirm revocation semantics, and design your secrets, monitoring, and incident playbooks accordingly.

If you operate multi‑tenant agents that require per‑user authorization across tenant APIs, architect OBO into your gateway. Verify IdP behavior, revocation linkage, and AgentCore runtime semantics before you trust it in production.