こんにちは、ネクストモード株式会社テクニカルサポート担当です。
前回の記事では、Amazon Bedrock AgentCore Runtime 上の自作エージェントを Okta for AI Agents に取り込み、「Where are my agents?(エージェントはどこにいるか)」までをご紹介しました。あわせて、次回以降は Slack や Notion などの MCP 連携と、接続・認可の制御(許可/拒否)を検証していく、と予告していました。
今回はそのうち、認可の土台にあたる部分を先に確認します。前回取り込んだ AgentCore エージェントを題材にしますが、今回は AgentCore Runtime にはまだ組み込まず、ローカル CLI からトークン交換だけを実行します。取り込んだエージェントを Active にし、ユーザーの id_token から ID-JAG を介して scoped access_token を得るトークン交換フローと、Resource connections による許可/拒否までを確認します。AgentCore Runtime や Slack MCP など実ツールへの適用は次回以降に行う予定です。
前回の記事
今回の検証でのポイントは次の2つです。
id_token → ID-JAG)access_token)画面操作とトークンの流れを中心に見ていきます。実装の細部は、必要に応じて公式ドキュメントを参照してください。
※本記事は執筆時点の情報に基づきます。Early Access や仕様変更により、画面や挙動が変わる場合があります。最新情報は公式ドキュメントをご確認ください。
id_token → ID-JAG → access_token の流れ今回はやらないこと:
今回の検証では、AgentCore Runtime に組み込む前段階として、ローカル CLI からトークン交換だけを実行します。ユーザーは OIDC Web App にログインして id_token を取得し、その id_token をエージェント資格情報を持つローカル CLI が受け取ります。
その後、ローカル CLI はまず Org Authorization Server に id_token を提示して ID-JAG を取得します。続いて、その ID-JAG を Custom Authorization Server に提示し、検証用スコープ xaa:read を持つ scoped access_token を取得します。Resource connections は、この Custom Authorization Server に対するアクセスを許可するかどうかを判定する位置づけです。
検証構成の図に出てきた各要素の役割を整理しておきます。
| 対象 | 役割 |
|---|---|
| OIDC Web App | 人がログインし、id_token を発行する |
| Org Authorization Server | 「誰の代理か」を確定(id_token → ID-JAG)。Delegations の Confirm authorization server 設定項目では、ここを指定する |
| Custom Authorization Server | 「何をしてよいか」を狭く発行(ID-JAG → access_token)。今回の検証で Resource connections の許可対象にする認可サーバー |
| AI Agent(Credentials) | private_key_jwt で自分だと証明し、交換リクエストを投げる |
利用順を箇条書きにすると次のとおりです。
id_token を得るid_token → ID-JAG を行う(Delegations が効く)access_token を行う(Access Policy が効く)ここでやや分かりづらいのが、Authorization Server が複数出てくる点です。今回の記事では、次の2つを分けて考えると整理しやすくなります。
id_token を確認し、エージェントがそのユーザーの代理として動いてよいかを確認したうえで ID-JAG を発行する役割を持ちます。xaa:read のような必要なスコープだけを持つ access_token を発行します。ここで少しだけ、ID-JAG を介して access_token を取得する意味を補足しておきます。
先にポイントを言うと、id_token と access_token は役割が違うためです。通常の OAuth / OIDC では、まずユーザーがアプリにログインし、アプリは「このユーザーは誰か」を示す id_token を受け取ります。ただし、id_token は基本的に「ユーザーがこのアプリにサインインしたこと」を示すためのトークンであり、そのまま別のリソースや MCP サーバーを呼び出すための access_token ではありません。
まず、比較対象として通常の OIDC ログインだけを見ると、流れは次のようになります。
ここで得られる id_token は、あくまで「ユーザーが OIDC Web App にログインしたこと」を示すものです。つまり、この時点では「ユーザーが誰か」は分かりますが、「エージェントがそのユーザーの代理として、どのリソースに何をしてよいか」までは決まっていません。
そこで Cross App Access / ID-JAG では、標準的な OIDC ログインのあとに、次の流れを追加します。
id_token を提示します。Okta は「その id_token がこのエージェント向けに発行されたものか」「ユーザーが有効か」「このエージェントがユーザーの代理として動いてよいか」を確認します。問題なければ、Okta は ID-JAG(Identity Assertion JWT Authorization Grant)を返します。audience、有効期限などを検証し、Access Policy や Resource connections の許可設定を見たうえで、必要なスコープだけを持つ access_token を発行します。この追加部分だけをクローズアップすると、次の流れになります。
通常の OIDC ログイン図と並べて見ると、id_token をそのまま API 用トークンとして使うのではなく、Org Authorization Server が id_token を検証して ID-JAG を発行し、その ID-JAG を Custom Authorization Server に提示して必要なスコープだけを持つ access_token を発行してもらう流れだと分かります。
つまり、ID-JAG は最終的な API 呼び出し用トークンではなく、「このエージェントは、このユーザーの代理として、このリソースに向かおうとしている」ことを示す Identity Assertion JWTです。最終的に API や MCP サーバーへ渡すのは、ID-JAG をもとに発行される access_token です。
今回の検証では、一般的な SaaS 側の Authorization Server の代わりに、Okta の Custom Authorization Server をリソース側の認可サーバーとして使っています。そのため、登場人物は少し違いますが、考え方は同じです。Token Exchange で「誰の代理か」を確定し、JWT Bearer Grant で「何をしてよいか」を狭く発行する、という分離がポイントです。
ID-JAG や Cross App Access の背景は、過去記事の ID-JAG の設計思想から読み解く Cross App Access の背景と技術の裏側 でも整理しています。また、Okta Developer Blog の Cross-App Access sequence diagram でも、標準的な OIDC ログインのあとに追加のトークン交換を行う流れが紹介されています。
ここからが今回の検証の本編です。まずは Okta Admin Console 上で、トークン交換に必要なエージェント、Authorization Server、Delegations、Resource connections を設定します。
設定の流れは以下のとおりです。
id_token を取得するための OIDC Web App を作成し、Delegations でエージェントと紐づけます。AGENT_CLIENT_IDAGENT_KEY_IDAGENT_PRIVATE_KEY_JWK次に、ID-JAG を提示して scoped access_token を発行してもらうための Custom Authorization Server を用意します。
今回の検証では、新規 Custom Authorization Server を作成しました。例として、Name は agentcore-demo-as としています。
xaa:read を追加します。AgentCore Demo Token Exchange)。xaa:read を許可します。
以降の手順で Resource connections に Custom Authorization Server を追加したり、ローカル CLI からトークン交換を実行したりする際に、この Custom Authorization Server の Issuer と ID を使います。Issuer の例は次のとおりです。
https://<your-okta-domain>/oauth2/ausXXXXXXXX
このときの ausXXXXXXXX が OKTA_CUSTOM_AS_ID です。
続いて、ユーザーの id_token を取得するための OIDC Web App を作成します。エージェントをインポートしただけでは、ユーザーの id_token は取得できないため、ログイン用の Web アプリを別途用意します。
AgentCore Demo User Sign-Inhttp://localhost:8765/callback(ローカル検証用)続いて、エージェントの Resource connections に、作成した Custom Authorization Server を追加します。ここで許可したリソースに対してだけ、エージェントがトークン交換を進められることを確認します。
agentcore-demo-as を選択し、Scopes で xaa:read を選択し、Add をクリックします。
ここまで設定できたら、ローカル CLI からトークン交換を実行します。検証では AgentCore Runtime にはまだ組み込まず、トークン交換だけのローカル CLI を使いました。まずは Okta 側の設定とトークン交換の流れが正しいことを、最小構成で確認するためです。
確認する内容は次の2つです。
access_token が発行されることid_token を取り、交換する| 変数 | 用途 |
|---|---|
OKTA_DOMAIN |
Org ドメイン(https:// なし) |
OKTA_CUSTOM_AS_ID |
Custom Authorization Server の ID(aus...) |
OKTA_SCOPE |
xaa:read |
AGENT_CLIENT_ID / AGENT_KEY_ID / AGENT_PRIVATE_KEY_JWK |
エージェント資格情報 |
OKTA_OIDC_CLIENT_ID / OKTA_OIDC_CLIENT_SECRET |
OIDC アプリ(id_token 取得用) |
今回は、id_token の取得と ID-JAG を介したトークン交換を確認するために、ローカルで実行する検証用スクリプトを用意しました。スクリプトでは、OIDC ログインで取得した id_token を環境変数として渡し、エージェント資格情報を使ってトークン交換フローを実行します。成功時は access_token と有効期限などが返ります。
以下は成功時のログです。
ここではコードの詳細には踏み込まず、検証用スクリプトの中で行われるトークン交換の流れだけを整理します。
client_assertion を作成します。
private_key_jwt を署名します。id_token を提示し、ID-JAG を発行してもらいます。
/oauth2/v1/tokentoken-exchangeid_tokenid-jagaccess_token を発行してもらいます。
/oauth2/{customAsId}/v1/tokenjwt-beareraccess_token を取得要するに、最初に Org Authorization Server で「誰の代理か」を確認し、その結果として発行された ID-JAG を使って、Custom Authorization Server から「何をしてよいか」を表す access_token を取得する流れです。
これで「Resource connections で許可されたリソースに対してだけトークン交換が通る」ことを観測できました。
今回の検証で実際にはまったのは、Delegations の Confirm authorization server という設定項目でした。
この設定項目では、「OIDC Web App から取得した id_token をどの Authorization Server で確認するか」を選びます。今回の構成では、id_token を確認し、「このエージェントがユーザーの代理として動いてよいか」を判断する Org Authorization Server を選択します。
最初はここに Custom Authorization Server(agentcore-demo-as)を選んでしまい、トークン交換時に次のエラーになりました。
'subject_token' is invalid: the client application is not registered for delegation to this agent.
Custom Authorization Server は、Org Authorization Server から発行された ID-JAG を受け取り、scoped access_token を発行するために使います。Delegations の Confirm authorization server で選ぶ対象ではないため、役割を分けて理解しておくと設定時に迷いにくくなります。
今回は、前回インポートした AgentCore エージェントを題材にしつつ、AgentCore Runtime にはまだ組み込まず、ローカル CLI から Okta for AI Agents のトークン交換フローと Resource connections による許可/拒否を確認しました。
今回の検証で重要だったのは、トークン交換そのものだけでなく、エージェントがユーザーの代理として動くときに、Okta 側でどこまで制御できるかを確認できた点です。
id_token をそのまま使うのではなく、Org Authorization Server で ID-JAG を発行し、Custom Authorization Server で scoped access_token に落とし込むことで、「誰の代理か」と「何をしてよいか」を分けて扱えることが分かりました。さらに、Resource connections の許可が有効な場合はトークン交換が成功し、許可を外すと失敗することも確認できました。
今回の検証は、AgentCore Runtime や Slack MCP などの実リソースへ接続する前段階です。ユーザー代理のトークン取得と、Resource connections による許可制御の土台を確認できたことで、次の検証では AgentCore Runtime、MCP、Okta for AI Agents を組み合わせ、実際の MCP 接続をどこまで安全に制御できるかにフォーカスします。