AIエージェント時代における認可の仕組み
はじめに
最近、クライアント社内でAIエージェント構築基盤を検討する機会があり、その中で「サブエージェントを含む複数のAIエージェントが、ユーザーの代わりに様々なツールやデータにアクセスするとき、認可ってどう設計するのが正解なんだろう?」という問いにぶつかりました。
正直、私は恥ずかしながらOAuthやOpenID Connect(以下、OIDC)周りの仕様にずっと苦手意識を持っていました。時にはシーケンスフローの図を見るたびに矢印の数に圧倒され、時にはクライアントとの会話についていけず悔しい思いをし、時にはAIエージェント全体アーキテクチャグランドデザインに落とし込む際に認可周りの仕組みの知識のなさから自分自身を恨むこともありました。
しかし、AIエージェント基盤を真面目に設計しようと思うと、もうそんな悠長なことは言っていられません。重い腰を上げて『OpenID Connect入門』(土岐孝平さん著)を読み込み、そこから派生して Token Exchange (RFC 8693) や ID-JAG(Identity Assertion Authorization Grant) という仕様に辿り着きました。
(既にクライアント社内でもキーワードとして出ていた話でしたが・・)
自分でもちゃんと調べてみると「これ、AIエージェント時代の認可問題に対するかなり有力な答えなのでは?」と改めて感じたので、自分の理解を整理する意味も込めて記事にまとめてみます。同じく認可周りで悩んでいる方の助けになれば幸いです。
なお、本記事を書くにあたっては IIJ Engineers Blog の以下の記事を大いに参考にさせていただきました。具体的なフローの解説などはこちらが非常に丁寧なので、本記事と併せて読まれることをおすすめします。
OAuth 2.0が抱える課題と代表的な拡張仕様
OAuth 2.0(RFC 6749)はもともと、「ユーザーがブラウザの前に座っていて、自分の意思で『このアプリにアクセスを許可します』ボタンを押す」ことを前提に設計された仕様です。2012年のRFC 6749制定からもう10年以上経ちますが、当時はまさかAIエージェントが人間の代わりに外部APIを叩きまくる時代が来るとは想定されていなかったでしょう。
そのため、現代的なユースケースに対応するために様々な拡張仕様が生まれています。代表的なものを挙げると以下のとおりです。
- OpenID Connect: OAuth 2.0は認可の仕組みだったが、無理やり認証にも使う開発者が増え、その結果アクセストークンの横取りなどによるセキュリティ脆弱性が見つかり、認証の仕組みを共通仕様として強化。
- PKCE(RFC 7636): ネイティブアプリやSPAなど、クライアントシークレットを安全に保持できない「パブリッククライアント」向けの拡張。認可コード横取り攻撃への対策。
- JWT Bearer Grant(RFC 7523): クライアント認証や、JWTを使ったアクセストークン取得を可能にする仕様。
- Token Exchange(RFC 8693): あるトークンを別のトークンと「交換」するための汎用仕様。後述するID-JAGの土台。
- ID-JAG(Identity Assertion Authorization Grant、ドラフト仕様): IdPが発行したIDアサーションを使って、ユーザー操作なしにアクセストークンを取得する仕組み。
私がOAuthやOpenID Connectをはじめとした認証・認可をしっかりと学ぶ前にも聞いたことのあるワードではありましたが、「なんでこんな面倒なことしないといけないんだ」と思っていたんですが、実際に攻撃者やID管理者目線でどのような欠点があるかという事例も調べていくうちに「あ、これ必要だわ…」と腑に落ちたものばかりです。
仕様は背景を知らないと納得できないですね。
認可コードフローのおさらい
AIエージェント時代の話に入る前に、もっとも基本的な 認可コードフロー をざっくりおさらいしておきます。
ここでは、最近のWebアプリで一般的な SPA 構成を例に、登場人物を「ユーザー」「ブラウザ」「SPA(Client)」「Webサーバ」「IdP」の5つに分けてシーケンス図にしてみました。
文章でも流れをまとめておきます。
- ユーザーがブラウザでアプリにアクセスし、SPAが読み込まれる。
- ユーザーがログインボタンを押すと、SPAはブラウザを経由してWebサーバの
/loginを叩く。 - Webサーバは
stateと PKCE 用のcode_verifier/code_challengeを生成し、セッションに保存。ブラウザをIdPの認可エンドポイントへリダイレクトさせる。 - ユーザーがIdPで認証し、同意画面で「許可」を押す。
- IdPが認可コードをクエリパラメータに付けて、Webサーバの
/callbackにリダイレクト。 - Webサーバは
stateを検証した後、認可コードとcode_verifierをIdPのトークンエンドポイントに送り、アクセストークン・IDトークン・リフレッシュトークンを取得。 - 取得したトークンはWebサーバのセッションに保存し、ブラウザにはセッションCookieだけを返す。SPAから見ると、以降はそのCookieを付けてWebサーバ経由でAPIを叩くだけ。
ここで重要なのは、「ユーザーの明示的な同意」がフローに組み込まれている ということです。「Googleでログイン」ボタンを押すと出てくるあの同意画面ですね。
このフローは、人間1人が1つのアプリを使う、という前提では非常によくできています。
AIエージェント時代の認可コードフローの問題
ところが、AIエージェント、特に 複数のサブエージェントが連携して動く構成 を考え始めると、認可コードフローでは色々と苦しくなってきます。
1. ユーザー同意のUXが破綻する
たとえば「今週のSlack DMを要約して、Notionの議事録ページに追記し、関連するGoogle Driveのファイルを整理しておいて」という指示をAIエージェントに出すとします。これを認可コードフローでやろうとすると、
- Slackの同意画面
- Notionの同意画面
- Google Driveの同意画面
…と、ユーザーは延々と「許可する」ボタンを押し続けることになります。一度押したらしばらく覚えてくれるとはいえ、サービスが増えるほど初期設定の負荷は線形に増えていきます。
しかも、エージェントが内部で別のサブエージェントを呼び出す構成にすると、「サブエージェントごとに同意取れって言われてもな…」という話になりがちです。
2. サブエージェントへの権限委譲が定義されていない
OAuth 2.0には「あるエージェントが取得したトークンを、別のサブエージェントに安全に委譲する」という概念が標準化されていません。
ナイーブにやるとすればアクセストークンをそのまま渡すことになりますが、これは典型的な「過剰委譲」のアンチパターンです。サブエージェントには本来、その仕事に必要な最小限の権限だけ持たせたいはずです。
私自身、個人でAWS上で学習のために開発する際にはとりあえずAdministrator権限をつけてIAMユーザー作っちゃえといってやってますが、実際の現場では最小権限の原則に沿って運用されるのが常識です。
これはIAMユーザーに限らず、AIエージェントなどでも同じ考えです。
3. そもそも「ユーザーがブラウザの前にいない」前提がある
スケジューリング実行されるエージェントや、バックグラウンドで動くエージェントの場合、認可コードフローの大前提である「ユーザーが操作できる」が成立しません。リフレッシュトークンで凌ぐにしても、最初の認可は人間が必要、という制約は残り続けます。
ID-JAGのメリット
こうした問題に対して、有力な解決策の1つとして注目されているのが ID-JAG(Identity Assertion Authorization Grant) です。これはIETFのOAuth WGで議論されているドラフト仕様で、ざっくり言えば、
IdP(Identity Provider)が発行したIDアサーションを使って、ユーザーの操作なしにリソースサーバー向けのアクセストークンを取得できるようにする
というものです。
ポイントを私なりにまとめるとこんな感じです。
メリット1: ユーザーの「許可するボタン連打」が不要になる
社員が会社のIdP(Okta、Microsoft Entra ID、Google Workspace など)にログインしている時点で、その人がアクセスできるリソースは原則決まっています。そこに対して、改めて各SaaSの同意画面を出すのはぶっちゃけ二度手間です。
ID-JAGでは、IdPが「この人は確かに弊社の田中太郎で、この権限を持っています」というアサーションを発行し、それをエージェントが受け取って各SaaSのアクセストークンに交換します。ユーザーは追加で何かをクリックする必要がありません。
メリット2: IdP中心の権限管理が可能になる
わかりやすく例えとして例をあげると、社員の入退社や異動に伴う権限変更を、IdP側で一括管理できる、というのは運用上めちゃくちゃ嬉しいです。
かつていろいろなSaaSサービスを導入したはいいけど、社内IdPと紐づいておらず、退職や案件離脱処理でSaaSごとにアカウントを止めて回る作業は苦しく、何より見逃しリスクもあるため運用が厳しかった思い出があります。
というか今も少しあります・・
IdP連携が整っていれば、IdPで止めれば連動して各SaaSのアクセスも止まります。
メリット3: スコープを絞ったトークン交換ができる
ID-JAGは内部的にToken Exchange(RFC 8693)の枠組みを使うので、「このエージェントには Slack の channels:read だけ」 といったように、スコープを絞ったトークンを発行することが自然にできます。
これがサブエージェント設計と相性が良くて、「親エージェントは広い権限、サブエージェントは絞った権限」という階層的な権限委譲が綺麗に組み立てられます。最小権限原則をシステム的に強制できる、というのが嬉しいポイントですね。
メリット4: 監査ログが取りやすい
トークン交換のたびに認可サーバーを経由するため、「誰が、いつ、どのエージェントに、どのスコープのトークンを渡したか」が認可サーバー側に集約されます。インシデント発生時の追跡可能性という観点で、これは大きい。
ID-JAGの仕様をもう少し詳しく見てみる
ここまでメリットの話を中心にしてきましたが、「で、結局のところ何がどうなってるの?」というところに踏み込まないと現場で実装する人は動けません。なので、もう一段階仕様レベルに降りて、特に JWT Assertion(IDアサーション)の中身 を覗いてみます。
仕様の位置づけ
ID-JAG(Identity Assertion Authorization Grant)は、IETF OAuth WGで議論中のドラフト仕様 draft-ietf-oauth-identity-chaining です。位置づけとしては、
- Token Exchange(RFC 8693) をベースに、
- JWT Bearer(RFC 7523) の考え方を取り込んだ
全体の流れ(2段階のトークン交換)
ID-JAGは大きく2つのステップで動きます。
- ステップ1: IDアサーションの取得 — クライアント(AIエージェント)が、認証済みユーザーに紐づくIDアサーション(JWT)をIdPから取得する。
-
ステップ2: アクセストークンとの交換 — そのIDアサーションを対象リソース側の認可サーバに送り、スコープ付きのアクセストークンに交換する。
図にすると以下のようなイメージです。
JWT Assertion(IDアサーション)の中身
実際のJWTのペイロードを書き下すと、例としてこんな感じになります。
{
"iss": "https://idp.example.com",
"sub": "user-12345",
"aud": "https://auth.slack.example.com",
"client_id": "ai-agent-platform",
"iat": 1714377600,
"exp": 1714377900,
"jti": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"scope": "channels:read chat:write"
}
各クレームの意味は以下のとおりです。
| クレーム | 意味 | 補足 |
|---|---|---|
iss |
IDアサーションを発行したIdPの識別子 | 認可サーバはこの値で「信頼するIdPか」を判定する |
sub |
アサーションの主体(ユーザー)の識別子 | IdP側のユーザーID |
aud |
このアサーションを受け取るべき認可サーバ | アサーションの宛先 を明示する重要なクレーム |
client_id |
アサーションを要求したクライアント | エージェント基盤の識別子が入る |
iat |
発行時刻(Unix時間) | |
exp |
有効期限 | 短く(数分)設定するのが推奨 |
jti |
アサーションのユニークID | リプレイ攻撃対策で使う(OIDCのnonceみたいなもの?) |
scope |
要求するスコープ | 任意。最小権限で絞っておくのが鉄則 |
仕様を読んでいて私が「なるほど」と感じたポイントを3つ挙げると、
-
audが認可サーバを名指ししている ので、「このアサーションはSlack向け」「これはNotion向け」と物理的に流用できないようになっている。1枚のアサーションで複数のSaaSを叩き回す、という危険な使い方がそもそもできない設計になっているのが地味に偉い。 -
expを短く設定する(1〜5分程度)のが推奨で、漏洩しても被害が限定的。アクセストークンと違って「リフレッシュして使い回す」のではなく、必要になったら都度発行し直す思想。 -
jtiを認可サーバ側で記録して、同じJWTの再利用を防ぐ。リプレイ攻撃対策としてシンプルで効果的。
ヘッダ部分も一応見ておくと、
{
"alg": "ES256",
"typ": "oauth-id-jag+jwt"
}
注目したいのは typ フィールドです。JWTのメディアタイプとして id-jag+jwt を明示することが推奨されています。これは 「このJWTはID-JAG用なので、汎用のJWT検証ロジックで誤って通してはいけませんよ」 というガードになります。
正直に言うと、私はそれまでJWTのtypなんて気にしたこともなく、「とりあえずJWTでしょ」くらいの認識でした。RFC 8725(JWT BCP)あたりを読んでようやく重要性が腹落ちしたので、似たような感覚の方は一度目を通しておくと吉です。
トークン交換リクエストの実体
JWT Assertionを認可サーバに渡すときのHTTPリクエストはこんな形になります。
POST /oauth2/token HTTP/1.1
Host: auth.slack.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client credentials>
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<JWT Assertion (Base64URL encoded)>
&subject_token_type=urn:ietf:params:oauth:token-type:id-jag
&resource=https://api.slack.example.com
&scope=channels%3Aread%20chat%3Awrite
ポイントは subject_token_type に ID-JAG用に新しく定義されたトークンタイプ (urn:ietf:params:oauth:token-type:id-jag) を指定するところです。これにより、認可サーバは「これはID-JAGのアサーションだから、この検証ルールで処理しよう」と判断できます。
認可サーバ側の検証ステップ
認可サーバが受け取ったJWT Assertionに対して行う検証は、ざっくり以下の通りです。
-
署名検証:
issからJWKS(公開鍵)を取得して、JWTの署名を検証する。 -
発行者検証:
issが認可サーバの「信頼するIdPリスト」に含まれていること。 -
オーディエンス検証:
audが認可サーバ自身の識別子と一致すること。 -
有効期限検証:
expが現在時刻より後で、iatが過度に古くないこと。 -
リプレイ防止:
jtiが一定期間内に使われていないこと(DBやキャッシュで管理)。 -
クライアント検証:
client_idが登録済みクライアントで、リクエスト元と一致すること。 -
スコープの妥当性検証: 要求された
scopeがそのユーザー × クライアントに対して認められるものであること。
これらを通って初めて、アクセストークンが発行されます。「JWTを受け取ったらすぐに信じる」のではなく 毎回かなり厳密に検証する のがポイントで、ここを手抜きすると一気に脆弱性の温床になります。
aud チェックを忘れていると、アクセストークンを攻撃者が悪用して利用できてしまうので大事です。
信頼関係(フェデレーション)の事前構築
ID-JAGが成立する大前提として、IdPと認可サーバ(各SaaSのバックエンド)の間に事前の信頼関係 が必要です。具体的には以下のような設定が事前に必要になります。
- IdPの公開鍵(JWKSエンドポイントのURL)が認可サーバに登録されている。
- IdPの
iss値が認可サーバの「信頼できる発行者リスト」に入っている。 - スコープのマッピング(IdP側の属性 ↔ 認可サーバ側の権限)が合意されている。
このフェデレーション設定の運用が、現実世界では一番の難所だと私は感じています。自社で完結するなら良いのですが、外部SaaS相手だと「対応してくれるかどうか」がそもそもの問題になります。
ID-JAGを利用した認可アーキテクチャ例
ここまでの話を踏まえて、AIエージェント基盤での認可アーキテクチャの一例を描いてみます。
┌────────────────┐
│ User │
└────────┬───────┘
│ 1. ログイン(SSO)
▼
┌────────────────┐
│ IdP │ ← 例: Okta / Entra ID
└────────┬───────┘
│ 2. ID Assertion (JWT)
▼
┌─────────────────────────────────────────┐
│ AI Agent Platform (Orchestrator) │
│ ┌────────────┐ ┌─────────────────┐ │
│ │ Main Agent │──▶ │ Sub Agent A │ │
│ └────────────┘ └─────────────────┘ │
│ │ │ │
└─────────┼──────────────────┼────────────┘
│ 3. ID-JAG │ 3'. Token Exchange
▼ ▼
┌─────────────────────────────────────────┐
│ Authorization Server (per Resource) │
└─────────┬───────────────────────────────┘
│ 4. Scoped Access Token
▼
┌─────────────────────────────────────────┐
│ Resource Server (Slack / Notion / ...) │
└─────────────────────────────────────────┘
流れを文字でも書くと、
- ユーザーがIdPでSSOログインする(普段の業務開始時に1回やるだけ)。
- IdPがIDアサーション(JWT形式のID Token相当)を発行。
- AIエージェント基盤(オーケストレーター)が、IDアサーションを使って各リソースの認可サーバーに対し ID-JAG(grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer 系) でトークン要求を行う。サブエージェントに委譲する場合は、Token Exchangeでスコープを絞った別トークンを発行。
- 取得したアクセストークンを使って、各SaaS(Slack、Notion、Google Drive など)のAPIにアクセス。
ここで実装上のポイントとして重要なのは以下です。
- エージェント基盤がトークンを長期保存しない設計 にできる。必要なときにIDアサーションから都度交換すればよい。
- サブエージェントに渡すトークンは短命・限定スコープ にする。サブエージェントの実装にバグがあっても、被害範囲を最小化できる。
-
IdPと認可サーバーの信頼関係を事前に構築 する必要がある(federation設定)。ここが運用上の肝になります。
このアーキテクチャを最初に紙に書き出したとき、「あ、これでようやくAIエージェントを安心して社内に展開できる絵が描けるな」と思いました。逆に言うと、この絵が描けないままエージェントを増やしていくと、後で認可周りの負債が雪だるま式に膨らむ未来しか見えません。
まとめ
- OAuth 2.0の認可コードフローは「人間がブラウザの前にいる」前提で作られていて、AIエージェント時代には色々と苦しい。
- 同意画面の連打、サブエージェントへの権限委譲、トークン管理の複雑さなど、ナイーブにやると詰むポイントが多い。
- ID-JAG は、IdPのIDアサーションを使ってユーザー操作なしでアクセストークンを取得できる仕組みで、AIエージェント基盤との相性が非常に良い。
- Token Exchangeと組み合わせることで、サブエージェント単位で最小権限のトークンを発行する階層的な認可設計ができる。
- まだドラフト仕様の段階ではあるものの、各IdPベンダーの動向を見ても、対応は時間の問題だと感じています。
最後に個人的な感想を一言。
『OpenID Connect入門』を読んだ後にこの辺りの仕様を追いかけてみて、「OAuthの拡張仕様って、結局のところ "現実世界のユースケース" を後追いで吸収してきた歴史なんだな」と感じました。AIエージェントというのも、その歴史の最新の一章なのかもしれません。
苦手意識があった分野で、AIエージェントアーキテクチャを整理するうえでの一番のボトルネックになっている部分でしたが、今回の自己学習やアウトプットにより前よりも解像度が上がってきた気がします。
まだまだ机上レベルでわかったつもりになっているだけだと思うので、もう少し解像度あげていけるように引き続き学習は進めていこうと思います。
どこかでAWSのCognitoやAgentCore周りのサービスを使った簡単な動作検証とかもやってみようかなと思うので、タイミングがあれば今後記事に書いていこうと思います!
同じように認可周りに対して苦手意識のある方やAIエージェントの認可で困っている方は是非参考にしてください。
それでは、よいAIエージェントライフを!!
参考文献
- 土岐 孝平 著『OpenID Connect入門 - アプリケーション開発者のための実践技術解説』
- RFC 6749 - The OAuth 2.0 Authorization Framework
- RFC 8693 - OAuth 2.0 Token Exchange
- RFC 7523 - JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- draft-ietf-oauth-identity-chaining (Identity Assertion Authorization Grant)
- ユーザの承認操作なしでアプリケーション連携を実現するOAuth拡張仕様 | IIJ Engineers Blog