DEV Community

Cover image for OAuthで実現するAIエージェントの安全な代理実行
Akira
Akira

Posted on Originally published at apidog.com

OAuthで実現するAIエージェントの安全な代理実行

エージェントが顧客のカレンダーを読み取り、顧客のアカウントからメッセージを送り、顧客名義でチケットを起票するとします。幅広い権限を持つサービスアカウントを1つ用意する方法は簡単ですが、すべての操作が同じ「インテグレーション」として記録されます。誰が何を起動したのか分からず、資格情報が漏洩すると、接続済みの全アカウントが危険にさらされます。

今すぐApidogを試す

正しい方法は委任認証です。ユーザーがスコープ限定・取り消し可能なトークンをエージェントに付与し、エージェントはそのユーザーとして動作します。監査証跡にはユーザーの名前も記録されます。これはOAuth 2.0の目的に合致します。

ただし、OAuthはブラウザと人間による「許可」操作を前提としています。一方、エージェントは午前3時にバックグラウンドで実行されることがあります。

本稿では、エージェント向けのOAuthフロー、スコープの設計、トークンの保存・更新・取り消し、実際のプロバイダーを使わずに認証フローをテストする方法を解説します。キーベース認証と委任認証で迷う場合は、APIキーとOAuthの比較から始めてください。

Apidogを使えば、エージェントが本番環境に到達する前に、期限切れや取り消しを含む認証フローの分岐を検証できます。

サービスアカウントと委任アクセス

2つのモデルは、適した用途と失敗モードが異なります。

サービスアカウント

サービスアカウントは、エージェント自身のIDと権限で動作します。次のような用途に適しています。

  • エージェント自身のデータベースを読む
  • 自社の内部サービスを呼び出す
  • インフラ上でスケジュール済みジョブを実行する

エージェント向けの最小権限APIキーで解説しているように、スコープを厳密に設定し、定期的にローテーションしてください。

委任アクセス

委任アクセスでは、エージェントが特定のユーザーとして、そのユーザーの権限だけを行使します。データが他人のものである場合は、原則としてこちらを選びます。

委任アクセスには、次の利点があります。

  • ユーザーが許可内容を確認できる
  • ユーザー単位でアクセスを取り消せる
  • すべての操作にユーザーIDを記録できる

組織全体へのアクセス権を持つサービスアカウントを「ユーザーの代わり」として使う設計は避けてください。動作はしますが、1つの漏洩した資格情報で全員が危険にさらされ、ユーザー単位の取り消しも、正確な監査証跡も実現できません。

エージェントに適したOAuthフロー

OAuth 2.0には複数のグラントタイプがあります。完全な仕様はOAuth 2.0仕様を参照してください。エージェントで主に使うのは次の4つです。

PKCE付き承認コードフロー

ユーザーとして操作する標準的なフローです。

  1. ユーザーをプロバイダーへリダイレクトする
  2. ユーザーがスコープを承認する
  3. サービスが認証コードをトークンと交換する
  4. 更新トークンを安全に保存する
  5. バックグラウンド実行では更新トークンを使う

PKCEがコード交換を保護します。OAuth 2.0セキュリティのベストプラクティスに従い、現在はすべてのクライアントタイプで推奨されるデフォルトです。承認コードグラントの詳しい手順も参照してください。

重要なのは、接続時と実行時を分離することです。

  • 接続時:人間がブラウザで一度だけ同意する
  • 実行時:エージェントが更新トークンを使い、人間の操作なしで動く

エージェント自身が同意フローを実行する必要はありません。

クライアントクレデンシャル

ユーザーが関与しないマシン間通信向けです。サービスアカウントには適していますが、ユーザーとして動作する用途には不適切です。同意するユーザーが存在しないためです。

デバイス認証グラント

ブラウザを持たないマシン向けのフローです。エージェントがコードを表示し、ユーザーがスマートフォンなど別の端末で承認します。CLIエージェントやヘッドレス環境で役立ちます。

トークン交換

RFC 8693のトークン交換を使うと、広い権限を持つトークンを、より限定的なトークンに交換できます。

たとえば、ユーザーのグラントから、1つのタスクに必要なスコープだけを持つトークンを発行し、それをサブエージェントに渡します。元のトークンをそのまま共有する必要はありません。

マルチエージェントシステムでは、エージェントごとの資格情報を実現する重要な仕組みです。マルチエージェントのハンドオフで解説している境界ルールにも適合します。

スコープは狭く、エージェント単位で設定する

委任アクセスの価値はスコープ設計にあります。しかし、多くの実装では、将来必要になるかもしれない権限をすべて要求してしまいます。

エージェントが実際に行う操作だけを要求してください。たとえば、予定の作成だけを行うエージェントに、メール、連絡先、ファイルへのアクセス権は不要です。

長い同意画面は、信頼性と影響範囲の両方の問題になります。OAuth 2スコープでは、プロバイダーがスコープをどのようにモデル化するかを説明しています。

実装時の原則

  • 接続時は最小限のスコープだけを要求する
  • 機能が必要になった時点で追加スコープを要求する
  • エージェントごとに異なるトークンを発行する
  • デフォルトでは読み取り権限を優先する
  • 書き込みや削除には明示的な昇格を要求する
  • 破壊的な操作には承認ゲートを設ける

たとえば、リサーチエージェントと経理エージェントが同じユーザーのために動作する場合、1つのトークンを共有しないでください。異なるスコープを持つ2つのトークンを発行します。これにより、リサーチエージェントが払い戻しを発行できなくなり、ログからどのエージェントが操作したかも分かります。

AIエージェントのガードレールで説明しているように、書き込み可能なトークンだけに安全性を依存しないことも重要です。

トークンの保存、更新、取り消し

トークンは資格情報です。通常の資格情報と同じように扱ってください。

保存

更新トークンは、ユーザーとエージェントのスコープをキーにして、保存時に暗号化します。

次の場所には決して書き込まないでください。

  • ログ
  • プロンプト
  • モデルへの入力
  • トレース
  • エラーメッセージ
  • ハンドオフ用のサマリー

エージェントのツール呼び出しをトレースする方法では、読み取り後ではなく境界で機密情報を編集する方法を解説しています。

更新

アクセストークンは短命であることを前提にします。更新処理はエージェントではなく、HTTPクライアントの前段に置いたトークンマネージャーが担当します。

  • 有効期限が近づいたら更新する
  • 401を受けた場合は1回だけ更新して再試行する
  • 更新トークンのローテーション結果を直ちに保存する
  • ユーザー単位で更新処理を直列化する
class TokenManager:
    def __init__(self, store, provider):
        self.store, self.provider = store, provider

    def access_token(self, user_id, agent_scope):
        rec = self.store.get(user_id, agent_scope)
        if rec.expires_in() > 60:
            return rec.access_token
        fresh = self.provider.refresh(rec.refresh_token, scope=agent_scope)
        self.store.save(user_id, agent_scope, fresh)   # ローテーション: 新しい更新トークンを保存
        return fresh.access_token

Enter fullscreen mode Exit fullscreen mode

特に次の2点に注意してください。

  1. プロバイダーによっては、更新のたびに新しい更新トークンを発行し、古いトークンを無効化します。新しいトークンをすぐに永続化しないと、ユーザーは再接続を求められます。
  2. 同じユーザーに対する更新を並行実行すると、トークンのローテーションで競合する可能性があります。ロックやキューを使い、ユーザーごとに更新を1つずつ処理してください。

取り消し

次の状態は、再試行ではなく終了として扱います。

  • ユーザーがアクセスを取り消した
  • トークンが失効した
  • 管理者がアカウントを削除した
  • プロバイダーが401または403を返した

認証失敗を繰り返しても解決しません。むしろ、レート制限や不正利用対策を発動する可能性があります。

エージェント向けAPIエラー設計に従い、ユーザーと不足しているスコープを特定した、次の行動が分かるエラーメッセージを返してください。

同意を実行から分離する

OAuthで最も扱いにくいのは、同意には人間が必要なのに、エージェントは無人で動くことです。

次のように設計すると管理しやすくなります。

  • 接続時:人間がブラウザで一度承認し、更新トークンを保存する
  • 実行時:エージェントが保存済みのグラントを使い、人間なしで実行する

これは、スケジュール実行やバックグラウンドエージェントの大半に適用できます。

事前に扱うべき制限

グラントは、数か月使われなかった場合やプロバイダーのポリシーによって失効することがあります。失効を検出したら、実行を停止してユーザーに通知してください。毎晩ひそかに再試行してはいけません。

また、ユーザーが一度も許可していないスコープを必要とする場合、エージェントが勝手に権限を昇格させてはいけません。追加同意を要求してください。

高リスクな操作には、実行時の承認ゲートも追加します。

  • トークン:エージェントが操作できることを証明する
  • 承認ゲート:その操作を実行すべきかを決定する

これは別の問いであり、両方を満たす必要があります。

本番前に認証フローをテストする

認証コードのパスは、プロバイダーの画面操作が必要なため、統合テストで見落とされがちです。最低限、次の5ケースをモックでテストしてください。

  1. ハッピーパス

    有効なアクセストークンで保護されたエンドポイントを呼び出し、成功する。

  2. アクセストークンの期限切れ

    プロバイダーが401を返す。トークンマネージャーが更新し、呼び出しを1回だけ再試行して成功する。

  3. 更新トークンの失効

    更新エンドポイントがinvalid_grantを返す。エージェントは停止して報告し、ループしない。

  4. スコープ不足

    プロバイダーがスコープエラー付きの403を返す。エージェントは再試行せず、不足しているスコープを伝える。

  5. 同時更新

    同じユーザーに対して2つの呼び出しを同時に発生させる。更新処理はちょうど1回だけ実行される。

Apidogでは、トークンエンドポイントと保護されたエンドポイントを定義し、エラーボディを含むレスポンスをモックできます。実際のプロバイダーに接続せず、認証マトリックス全体を実行できます。

本番環境ではなくモックに対してエージェントを実行する習慣や、リクエストレベルの詳細についてはOAuth 2 APIテストガイドも参照してください。

3つの統合例

カレンダーアシスタント

ユーザーの空き状況を読み取り、会議を予約します。

必要な設計は次のとおりです。

  • 委任アクセス
  • 読み取りと予約用の2つのスコープ
  • ブラウザでの接続時同意
  • その後のバックグラウンド実行
  • 取り消し時の停止と通知

ユーザーが統合を切断したら、夜間ジョブが無効なグラントを1週間再試行し続けるのではなく、すぐに停止してください。

共有受信トレイのサポートエージェント

チームに属するチケットを処理するエージェントです。

共有アカウントを使うことは、リソース自体がチームに属しているため、場合によっては合理的です。ただし、すべての返信が監査ログ上で同じIDに見えてしまいます。

より良い設計は、次の組み合わせです。

  • 独自のスコープを持つボットID
  • 実行を起動した人間のID
  • エージェント名と実行ID

これにより、エージェントを人間になりすまさせずに、責任の所在を追跡できます。

社内運用エージェント

自社インフラを再起動し、ダッシュボードを読み取ります。ユーザーデータや委任は関係ありません。

この場合は、狭いスコープを持つサービスアカウントが適切です。重点を置くべきなのは同意ではなく、資格情報のローテーションと影響範囲の制限です。

判断基準は所有者です。

  • データが、アクセスを取り消したいと考える可能性のある他者に属する:委任認証
  • データが自社に属する:サービスアカウントと厳格なスコープ管理

人間による要求者のIDを保持する

委任認証が示すのは「誰の権限で実行したか」です。「誰が要求したか」は別の情報です。

トークンは、エージェントがユーザーとして操作できることを証明します。しかし、どの人間が実行を要求したかまでは記録しません。

この2つ目のIDを、実行データと一緒に保持してください。タスク管理レイヤーは自然な保存場所です。Sharklyのタスクでは、作業の責任者を、実行を担当するエージェントまたはクルーとともに記録できます。Sharklyのドキュメントでは、この分離について詳しく説明しています。

保存方法にかかわらず、インシデント後の監査で問われるのは通常、「誰がこれを要求したのか」です。トークンだけでは答えられません。

モデルに資格情報を見せない

エージェントシステムの認証インシデントを減らす最も重要な設計原則の1つは、モデルにトークンを見せないことです。

トークンは、モデルがツールと引数を選択した後、HTTPレイヤーでエグゼキューターが注入します。

  • ツールスキーマにtokenパラメータを含めない
  • プロンプトに資格情報を含めない
  • モデルが読むレスポンスからAuthorizationヘッダーを除去する
  • トークンをログ、トレース、サマリーに保存しない

エージェントでは、モデル入力が多くの経路を通ります。コンテキストはハンドオフ用に要約されたり、トレースに保存されたり、エラーメッセージで返されたり、ユーザーに説明されたりします。

これらはすべて通常の機能です。資格情報がコンテキストに入った瞬間に、情報漏洩の経路になります。

ユーザーIDにも同じ原則を適用します。エグゼキューターは実行対象のユーザーを把握し、そこから適切なトークンを選択します。モデルにユーザー名やユーザーIDを決めさせることは、最も予測しにくいコンポーネントに認証判断を委ねることになります。

チェックリスト

  • [ ] 他者のデータには委任アクセス、自社リソースにはサービスアカウントを使う
  • [ ] 接続時はPKCE付き承認コード、ヘッドレス環境ではデバイスグラントを使う
  • [ ] エージェントごとに最小限のスコープを要求する
  • [ ] 必要になった時点で段階的にスコープを昇格する
  • [ ] サブエージェントには元のグラントではなく、交換済みトークンを渡す
  • [ ] 更新トークンを暗号化して保存する
  • [ ] トークンをプロンプト、ログ、トレースに含めない
  • [ ] トークン更新をマネージャーに集約する
  • [ ] ユーザー単位で更新を直列化する
  • [ ] ローテーションされた更新トークンをすぐに永続化する
  • [ ] 401403を終端エラーとして扱う
  • [ ] ユーザーと不足スコープを示すメッセージを返す
  • [ ] 失効したグラントを検出し、ユーザーに再接続を求める
  • [ ] 高リスク操作に承認ゲートを追加する
  • [ ] 5つの認証シナリオをモックに対してCIでテストする

委任認証は共有キーより手間がかかります。しかし、エージェントが他人のために操作するなら、次の2つを実現するために必要です。

  1. ユーザーがアクセスを取り消せること
  2. ログに誰が何をしたかを記録できること

Apidogをダウンロードして、エージェントを無人実行する前に、トークンフローと失敗ケースを構築・検証しましょう。

よくある質問

エージェント自身でOAuth同意フローを完了できますか?

いいえ。エージェントが同意を代行すべきではありません。同意には、何を許可するかを判断する人間が必要です。

人間が通常のブラウザフローで一度承認し、その後エージェントが保存済みのグラントを使う設計にしてください。

各エージェントは独自のOAuthクライアントを持つべきですか?

通常は、製品統合ごとにクライアントを分け、その中でエージェントごとに別のトークンを発行します。多くの場合、トークン交換を利用します。

次のケースでは、クライアントを分けるメリットがあります。

  • プロバイダーがクライアント単位でレート制限する
  • エージェントごとに独立して取り消したい
  • 障害や監査の境界を分けたい

更新トークンがローテーションされ、新しいトークンを保存し忘れたらどうなりますか?

古いトークンが無効になるため、ユーザーは締め出され、再接続が必要になります。

新しい更新トークンは、古いトークンを消費する処理と同じトランザクションで永続化してください。また、ワーカー間の競合を防ぐため、ユーザー単位で更新を直列化します。

モデルにアクセストークンを見せても安全ですか?

いいえ、安全ではありません。

トークンはHTTPレイヤーに属し、エグゼキューターが注入するべきです。モデルが見るデータは、トレース、サマリー、レスポンスとして後から現れる可能性があります。エージェント向けの最小権限APIキーでも、この原則を解説しています。

どのエージェントが何をしたかを監査するにはどうすればよいですか?

すべての呼び出しで、次の情報を記録します。

  • ユーザーID
  • エージェント名
  • 使用したスコープ
  • トークン識別子
  • 実行を要求した人間のID

トークンそのものは決して記録しないでください。エージェントのツール呼び出しをトレースする方法では、監査レコードの形式を説明しています。

プロバイダーがトークン交換をサポートしていない場合はどうすればよいですか?

プロバイダーが複数のグラントを許可しているなら、エージェントごとに個別のグラントを保存してください。

それもできない場合は、自社ゲートウェイでスコープ縮小を強制します。エージェントの呼び出しがネットワーク外へ出る前に、許可された操作だけを通過させてください。

Top comments (0)