Amazon Bedrock AgentCore Gateway を用いたマルチテナントエージェントのためのオンビハーフ・オブ・トークン交換の実装
AWS は Amazon Bedrock AgentCore Gateway に OAuth 2.0 トークン交換(RFC 8693)をネイティブ実装し、マルチテナント環境における正確な身元伝播と監査証跡の維持を実現した。
キーポイント
OAuth 2.0 トークン交換のネイティブサポート
Amazon Bedrock AgentCore Identity が RFC 8693 に準拠し、エージェントが直接実装せずとも、インバウンドユーザートークンを下流 API のオーディエンスに紐付いた新しいトークンへ透明に変換する機能を追加した。
マルチテナントにおける身元伝播の解決
エージェントが複数のテナントや下流サービスに対して呼び出しを行う際、元の呼び出し主(ユーザー)の ID を `sub` クレームを通じてエンドツーエンドで保持し、監査証跡を破綻させない仕組みを提供する。
混乱した代理問題と監査リスクの排除
従来のエージェントサービスIDでの実行(監査不能)やユーザートークンの直接転送(混乱した代理問題)という二つの課題に対し、オーディエンスバインディングによる防御層を構築し、スケーラブルなセキュリティを実現する。
実装ガイドと参照実装の提供
Okta と連携した完全なマルチテナント OBO 設定のウォークスルー、JWT クレーム変換の詳細、および「TravelBot」という参照実装(Acme と Globex の 2 テナント)を GitHub で公開し、現場での適用を容易にする。
重要な引用
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.
The original caller's identity is preserved end to end through the sub claim, even as the audience changes per tenant.
影響分析・編集コメントを表示
影響分析
この発表は、生成 AI エージェントが本番環境でマルチテナントかつ大規模に運用される際の最大の課題である「身元管理」と「監査可能性」に対する決定的な解決策を示すものです。OAuth 2.0 の標準規格をクラウドプロバイダーのゲートウェイ層に直接実装することで、開発者が個別に複雑なトークン交換ロジックを実装する負担を解消し、セキュリティとコンプライアンスのレベルを底上げします。
編集コメント
生成 AI エージェントのセキュリティ実装において、身元伝播と監査証跡の維持は長年の課題でしたが、AWS が標準プロトコルをネイティブサポートすることで、実務レベルでの解決が現実味を帯びました。
生成 AI エージェントをマルチテナントの生産環境に展開する際、特有のアイデンティティ問題に直面します。具体的には、エージェントがユーザーに代わって下流 API を呼び出す際、その呼び出しに伴うのは誰の身元なのかという点です。
エージェントのサービス ID で実行すると監査証跡が崩壊し、すべての下流システムがエージェントを無条件で信頼することになります。一方、ユーザーのトークンをそのまま転送すると、下流のツールは「混乱した代行者(confused deputy)」状態に陥ります。1 つのエージェントが多数のテナントを代表し、かつツールの呼び出し時にユーザーが存在しない状況では、どちらの選択肢もスケーラビリティに欠けます。
この課題に対し、OAuth 2.0 トークン交換仕様(RFC 8693)が解決策を提供します。Amazon Bedrock AgentCore Identity は、これをネイティブな資格情報プロバイダーグラントタイプとしてサポートしています。Amazon Bedrock AgentCore を用いたマルチテナントエージェントの構築 や Bedrock AgentCore Gateway インターセプターによるきめ細かいアクセス制御の実装 で、エージェントシステムにおけるオン・behalf・オブ(OBO)トークン交換の概念的な基盤が説明されています。本稿では、その実装ガイドとして、Okta に対する完全なマルチテナント OBO セットアップの手順を解説します。各ホップでの JSON Web Token (JWT) クレーム変換の流れを追跡し、オーディエンスバインディングによってどのようにテナント横断でスケーラブルかつ多層防御を実現できるかを実証します。
リファレンス実装である「TravelBot」は、複数のテナント(Acme と Globex を例示)に対応する予約アシスタントです。本記事で取り上げるリファレンス実装は、公開後に aws-samples/sample-obo-flow-poc リポジトリにて利用可能になります。
エージェントが複数の下流サービスやテナントを統括し、入力トークンの対象(オーディエンス)が単一の下流 API と一致しない場合、OBO パターンは不可欠です。一方で、入力オーディエンスがすでに下流サービスと一致する単一テナント向けエージェントであれば、単純なトークンの転送だけで十分なケースもあります。本記事では、主に多テナント環境におけるケースに焦点を当てて解説します。
AgentCore Identity におけるオン・ビューハーフ・オブ・トークン交換の紹介
Amazon Bedrock AgentCore Identity は、OAuth 2.0 トークン交換(RFC 8693)をネイティブな資格情報プロバイダーのグラントタイプとしてサポートしています。この機能により、AgentCore Gateway は下流ツールを呼び出す前に、エージェント側が交換処理を実装する必要なく、入力されたユーザートークンを自動的に新しい、対象限定のトークンに透明性を持って交換できます。
この機能には、以下のメリットがあります:
- テナント境界を越えたアイデンティティの継承 – サブクレームを通じて、テナントごとにオーディエンスが変化しても、元の呼び出し主のアイデンティティはエンドツーエンドで保持されます。
- 暗号化による最小権限 – 各下流への呼び出しには、aud クレームによって特定のサービスに紐付けられたトークンが付与されます。あるテナント向けに発行されたトークンを、別のテナントで利用することはできません。
- エージェント側での交換ロジック不要 – エージェントコードは単一のインバウンドトークンを取得してツールを呼び出すだけでよく、トークンの交換処理はすべて AgentCore が担当します。
- 標準ベースの実装 – この実装は RFC 8693 のトークン交換グラントに基づいて構築されています。同じグラントタイプと互換性のあるリクエスト形状をサポートする認証サーバーであれば、いずれも資格情報プロバイダーとして機能できます。
エージェント AI における混乱した代行者問題
「自分の予約状況を表示して」といったリクエストを、多数の異なるテナントから持つユーザーに対して処理するエージェントを想像してください。実装には主に3つの選択肢がありますが、そのうち正しいのはたった一つだけです。
- サービスアカウントのなりすまし – エージェントは自分自身として認証し、リクエストヘッダーやパスパラメータを通じてユーザーの身元を主張します。この場合、すべての下流 API はエージェントを無条件に信頼する必要があります。もしエージェントが侵害されれば、任意のテナントに対してあらゆるユーザーになりすまして行動できてしまいます。これは典型的な「混乱した副官」問題です。
- ユーザートークンの直接転送 – エージェントは、入力されたユーザートークンをそのまま下流 API の呼び出しに再利用します。この手法が機能するのは、入力トークンのオーディエンス(対象)が既に下流 API と一致している場合に限られます。マルチテナントシステムではこの条件が満たされることは稀であり、エージェントがツールゲートウェイの前面に出ている場合は絶対に成立しません。
- 代理権限付与によるトークン交換 – エージェント内の認可ブローカーが、入力された主体(subject)トークンを新しいトークンに交換します。この新トークンの主体は元の呼び出し元を指し、オーディエンスは下流 API を指し、署名は下流 API が信頼する認可サーバーによって発行されます。その結果、生成されるトークンは単一のユーザーに代わって行われる単一の下流呼び出しに対して暗号学的にスコープ限定されたものとなります。
代理権限付与(OBO)こそが、ユーザーの身元をエンドツーエンドで維持し、オーディエンス境界において最小権限の原則を強制し、エージェントへの信頼を必要とせず下流 API が独自に検証可能なトークンを生成する唯一の選択肢です。RFC 8693 の実装には、エージェントランタイム、認可サーバー、そして下流 API 間の連携が不可欠です。これらいずれかが設定ミスを起こせば、セキュリティ体制は静かに低下してしまいます。
Amazon Bedrock AgentCore Gateway と AgentCore Identity を活用すれば、こうした調整の負担は解消されます。Gateway がツール呼び出しをインターセプトし、対象テナントを特定した上で、下流への呼び出しを実行する前に、Identity にてテナントの認証サーバーに対してトークン交換処理を実行させる仕組みです。
名代(Impersonation)とオン・ビハーフ・オブ(On-Behalf-of)の違い
オン・ビハーフ・オブ(OBO)でのトークン交換では、インバウンドトークンのサブジェクト(sub)クレームは保持されたまま、聴衆(aud)クレームが下流サービス向けに書き換えられます。また、交換を行った主体の情報は、別のクレームとして記録されます(RFC 8693 に基づく act クレーム、または Okta の cid クレーム)。これにより、下流 API は単一のトークンから二つの重要な問いに回答できます。「誰の名代で行動しているのか?」(sub クレーム)と「実際に行動を実行したのは誰か?」(actor クレーム)です。権限付与の判断基準は sub に基づき、監査ログやレート制限の判定は actor に基づいて行うのが適切です。
名代処理とオン・ビハーフ・オブ処理の概念の違いについてさらに詳しく知りたい場合は、Bedrock AgentCore Gateway のインターセプターで細粒度アクセス制御を適用する をご参照ください。
以下の図は、TravelBot の予約ツールを用いて、両者のパターンを対比させたものです。ユーザートークンを直接転送する方式では、インバウンドと下流のトークンは同一のものになります。一方、OBO 方式では、各ホップで異なるテナントの予約 API に紐づく固有のトークンが使用されます。

図 1. TravelBot の予約ツールにおける、ユーザートークンの直接転送とオン・.behalf・オブ(代理)トークンフローの比較。
直接転送パターン(上段)では、エージェントはユーザーのトークンを変更せずにそのまま転送します。このトークンの aud クレームはエージェントの API 向けに発行されたものなので、下流のツール側で聴衆(オーディエンス)検証をスキップするか、上位サービスから受け取った任意の聴衆を受け入れるかのどちらかになります。どちらも「混乱した副官」の問題を再発させることになります。
一方、オン・behalf・オブパターン(下段)では、AgentCore が各ホップでトークンを交換し、単一の下流サービスに紐付いた aud クレームを持つ新しいトークンと、必要な最小限の scp を持つトークンを生成します。sub クレームはホップを跨いで維持されるため、監査や権限判断は依然として元のユーザーに対して行われます。一方、actor クレームには、交換を実行した委任者として AgentCore が記録されます。
ソリューション概要
本記事全体を通じて、以下の識別子は TravelBot 参照モデルにおける特定の Okta リソースを指します。
| 識別子 | 役割 |
|---|---|
| TravelBot Provider | エージェントに対してインバウンド JWT を発行する Okta 認証サーバー。 |
| ACME Travel API | ACME テナント向けに OBO トークンを発行する Okta 認証サーバー。 |
| Globex Travel API | Globex テナント向けに OBO トークンを発行する Okta 認証サーバー。 |
| TravelBot Agent Client | エージェントの機械間通信パス(レガシー/テスト用)における Okta API Services アプリ。 |
TravelBot ユーザークライアント
3 つのステップによるユーザーログイン用の Okta OpenID Connect (OIDC) アプリです。
AgentCore デリゲート
AgentCore Identity がトークン交換を実行する際に使用する認証情報を保持する Okta アプリです。
TravelBot の参照実装では、交換の両側で Okta を使用しています。1 つの Okta 認可サーバー(TravelBot プロバイダー)がエージェントを認証し、残りの 2 つ(ACME Travel API および Globex Travel API)が各テナントに対して OBO トークンを発行します。これら 3 つはすべて Okta の「カスタム」認可サーバーです。Okta に標準で用意されている組織用認可サーバーでは、カスタムのオーディエンスやスコープをサポートしていないため、このパターンには対応できません。
同等の機能を持つ他のアイデンティティプロバイダー(IdP)が同じ役割を担うことも可能です。RFC 8693 が認可サーバー層で実装されているため、トークン交換グラントをサポートする他の認可サーバーでも同様のアーキテクチャが動作します。AgentCore Identity が送信するリクエストの具体的な形状(subject_token_type、オーディエンス、アクター・トークンの有無、クライアント認証方式など)は、カスタムパラメータマップを通じて各認証プロバイダーごとに設定されるため、異なる IdP への対応はコードの変更ではなく構成変更で済みます。ただし、実際のデプロイ環境に合わせて検証を行うことを推奨します。
Auth0(カスタム トークン交換機能経由)や Keycloak(レルムごとにトークン交換機能を有効化)は、互換性のある RFC 8693 のサポートを謳っており、grantType: TOKEN_EXCHANGE とともに動作します。一方、AWS IAM Identity Center は「信頼されたトークン発行者(Trusted Token Issuer)」機能を通じて概念的に類似したパターンをサポートしていますが、そのリクエストとレスポンスの形状は RFC 8693 と異なります。これは前述の Okta の「信頼サーバー」の関係性とは区別されるべきものです。
Microsoft Entra ID のオン・.behalf.of フローでは、grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer に requested_token_use=on_behalf_of(RFC 7523)を指定し、RFC 8693 のグラントタイプとは使い分けられています。AgentCore Identity では、このフローをネイティブでサポートしており、認証プロバイダー側では grantType: JWT_AUTHORIZATION_GRANT を使用します。
Amazon Cognito のユーザープールは、外部から呼び出されるエージェントの認証を行うプロバイダー IDP として機能できます。Cognito をコンシューマー側のオン・behalf.of ロールに利用する場合は、AgentCore Identity のドキュメントで現在のグラントタイプ対応状況を確認してください。
AgentCore Identity は、パブリック接続に加え、テナントの認可サーバーや VPC 内にホストされた IDP への仮想プライベートクラウド(VPC)経由のプライベート接続もサポートしています。設定パターンについては、AgentCore の開発者ガイドにある [プライベートな IDP への接続] を参照してください。
このアーキテクチャには 6 つのコンポーネントが存在します。
- プロバイダー認証サーバー:エージェントがゲートウェイに提示するインバウンド JWT を発行します。TravelBot の場合、これは「TravelBot Provider」と名付けられた Okta 認証サーバーです。
- AgentCore Gateway:インバウンド JWT をプロバイダーの JSON Web Key Set (JWKS) と照合して検証し、ツール呼び出しを適切なテナント宛先にルーティングします。また、On-Behalf-Of (OBO) トークン交換の調整も担当します。
- AgentCore Identity:委任クライアント認証情報を保持し、RFC 8693 に基づくトークン交換をテナント認証サーバーに対して実行します。
- テナントごとの認証サーバー:各テナントのオーディエンスにスコープを限定した OBO トークンを発行します。TravelBot では、ACME Travel API と Globex Travel API はそれぞれ異なる Okta 認証サーバーであり、独自のオーディエンスとアクセスポリシーを持っています。
- テナントごとの API サーフェス:各テナントごとに JWT オーザライザーを備えた Amazon API Gateway HTTP API です。各オーザライザーは発行者、オーディエンス、必要なスコープを検証し、テナント間でのトークンの不正再利用を防ぐ最後の防御ラインとなります。
- テナント固有のビジネスロジック:検証された OBO トークンを受け取り、クレームを読み込んでテナントごとの判断を行う AWS Lambda 関数です。ユーザーごとに許可された
authorized_scopesクレームから書き込み権限を強制し、予約情報を Amazon DynamoDB に保存します。パーティションキーにはsubクレームを使用するため、ユーザーは自分自身の記録のみを読み取ることはできます。
この投稿の残りは、リクエストがこれらのコンポーネントを通過する順序に沿って解説していきます。以下の図でリクエストフローを要約しています:

図 2. AgentCore Gateway を用いた、オンビハーフ・オブ(OBO)リクエストの全体フロー。
このワークフローは、以下の手順で構成されています。
ユーザーは、3 つのステップからなる authorization_code ログインフローを通じてプロバイダーの認証サーバーで認証を行い、エージェントはその際に入力された JWT を受け取ります。この JWT はゲートウェイに対して限定(audience-bound)されています。
次に、エージェントは Model Context Protocol (MCP) を介してツールを呼び出します。この際、先ほど受け取った JWT をベアラートークンとして提示します。
Gateway はプロバイダーの JWKS を取得し、JWT の署名を検証するとともに、audience が「travelbot-provider」に一致しているかを確認します。
その後、Gateway は Acme 対象に対応する資格情報プロバイダーを選択し、Identity に OBO(On-Behalf-Of)トークンの発行を依頼します。
Identity は、RFC 8693 に基づくトークン交換リクエストを Acme の認証サーバーへ送信します。ここで、subject トークンは入力された JWT であり、要求される audience は「https://api.acme-travel.example」です。
Okta は、Acme の認証サーバーがプロバイダーの発行者を信頼しているかを確認し、委任クライアントに対するアクセスポリシーを適用します。さらに、ユーザーごとの権限付与スコープ(authorized_scopes)クレームを計算して署名付きの OBO JWT を発行します。
Identity は発行された OBO JWT を Gateway に返送します。
Gateway はこの OBO JWT をベアラートークンとして提示し、API Gateway 上の「/acme」ルートへアクセスします。
API Gateway の JWT オーザライザーは、発行者(issuer)、audience、および必要なスコープを検証した上で、リクエストを AWS Lambda 関数へ転送します。
Lambda 関数は OBO クレームをデコードし、authorized_scopes に基づく書き込み権限を確認します。その後、ユーザーのサブジェクト ID(sub)でパーティション分割された DynamoDB を照会し、テナント固有のレスポンスを返却します。
このレスポンスは、API Gateway と AgentCore Gateway を経由してエージェントへ戻されます。
3 つのステップからなるインバウンドログイン
TravelBot は実際のユーザーを認証する必要があるため、Gateway と通信する前に OAuth 2.0 の authorization_code フローを実行します。
- エージェントはランダムな状態値(state)を生成し、Okta の /authorize URL を構築します。ここで response_type=code、scope=openid email gateway/invoke を指定し、リダイレクト先にはローカルのコールバックリスナーを指す redirect_uri を設定します。
- 次にエージェントはコールバックリスナーを開始した上でブラウザを開きます。
- ユーザーが Okta で認証を行います。
- Okta は認証コードと状態値(state)を添えて、指定されたコールバックへリダイレクトします。
- コールバックリスナーは状態値を検証してクロスサイトリクエストフォージェリ(CSRF)攻撃を防ぎ、Okta の /v1/token エンドポイントで認証コードと交換し、インバウンドアクセストークンを取得します。
- エージェントはこのアクセストークン(そのサブジェクトは認証済みユーザーを指す)を、Gateway へのすべての呼び出しにおけるベアータンクとして使用します。
本番環境の Web アプリケーションでは、ローカルのコールバックリスナーはロードバランサー背後にあるアプリケーションサーバー上の通常のルートに置き換えられ、redirect_uri はその公開 HTTPS エンドポイントになります。トークンの交換ロジック自体は同じですが、リダイレクト先がどこになるかという点だけが異なります。
トークン変換の詳細解説
以下の表では、インバウンドトークンから OBO(On-Behalf-Of)トークンへ移行する際に、各 JWT クレームがどのように変換されるかを要約しています。sub クレームは最初から最後まで保持されます。それ以外のクレームはすべてテナントの認証サーバーによって書き換えられたり追加されたりします。
| Claim | Phase 1 (inbound) | Phase 3 (OBO) | Change |
|---|
発行元認証サーバーとテナント認証サーバーの間のトークン交換プロセスでは、以下のクレームがどのように処理されるかを確認できます。
- aud(対象):「travelbot-provider」から「https://api.acme-travel.example」へ書き換えられます。これはテナント側の API へのバインディングを意味します。
- sub(サブジェクト):「alice@acme-travel.example」は変更されず、元の呼び出し主として保持されます。
- cid(クライアント ID):発行元クライアント(TravelBot Agent Client)から、デレゲートクライアント(AgentCore Delegate)へ書き換えられます。これにより、実行主体と呼び出し主が区別されます。
- scp(スコープ):「[openid email gateway/invoke]」という広範な権限から、「[booking/read booking/write]」というテナント固有の権限に絞り込まれます。
- authorized_scopes(承認済みスコープ):新しい項目として「booking/read」が追加されます。
この交換プロセスにおいて、セキュリティを担保する重要な役割を果たすのは以下の 3 つのクレームです。
まず、sub クレームは最初から最後まで変更されません。これにより、監査ログや権限判断が常に元の呼び出し主(エンドユーザー)に紐付けられます。
次に、aud クレームはテナント API に書き換えられ、トークンが特定の downstream サービス(下流サービス)に対して暗号学的にバインドされます。
最後に、cid クレームには交換を実行したデレゲート(仲介者)の情報が記録され、実行主体と呼び出し主を明確に分離します。
なお、複数の IdP(ID プロバイダー)間での相互運用性を考慮する場合、クレーム名の扱いには注意が必要です。Okta ではスコープは「scp」配列として、アクターは「cid」として出力されます。一方、RFC 8693 や OAuth 2.0 の標準仕様では、スコープはスペース区切りの文字列(scope)、アクターはネストされたオブジェクト(act)として定義されています。複数の IdP にわたる監査ログを正規化する際は、仕様の形式に限定せず、これらの異なるデータ構造の両方を考慮する必要があります。
TravelBot の参照実装では、インバウンドトークンは 3 つのステップからなる authorization_code ログインフローを通じて取得されるため、sub クレームには認証された人間ユーザーが格納されます。一方、マシン間通信フロー(client_credentials グラント)の場合、sub には呼び出し元のアプリケーションのクライアント ID が設定されます。
フェーズ 1 — プロバイダー認可サーバーが発行したインバウンドトークン:
{
"iss": "https://example.okta.com/oauth2/aus",
"aud": "travelbot-provider",
"sub": "alice@acme-travel.example",
"cid": "0oa",
"scp": ["openid","email","gateway/invoke"],
"exp": 174839
}
原文を表示
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 and 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 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.
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 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:
{
"iss": "https://example.okta.com/oauth2/aus",
"aud": "travelbot-provider",
"sub": "alice@acme-travel.example",
"cid": "0oa",
"scp": ["openid","email","gateway/invoke"],
"exp": 174839
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み