ブログに戻る
アクセス制御されたデータを表す、暗い画面に流れる緑色のコード

社内 AI ワークフローにおけるきめ細かい OAuth スコープの実装

9分で読める

AI の自動化ワークフローでは、Gmail、Drive、Calendar といった機密性の高い Google サービスへのアクセスが必要になることがよくあります。多くの開発者は既定で、AI エージェントにフルアクセスの OAuth スコープを付与しています。これは社内の企業データにとって、許容できないセキュリティリスクを生みます。プロンプトインジェクションや脆弱性のある依存パッケージ経由でエージェントが侵害された場合、フルアクセス権限があれば、攻撃者は文書を外部へ持ち出し、悪意のあるメールを送信し、重要なレコードを削除できてしまいます。本ガイドで得られるものは明確です。厳密にきめ細かい読み取り専用の OAuth スコープを使い、安全な社内 AI ワークフローを設計する方法を学べます。このアプローチなら、AI 自動化の有用性を保ったまま、侵害が起きた際の影響範囲を最小限に抑えられます。

広すぎるスコープが生むセキュリティリスク

LangGraph のようなフレームワークで社内ツールを構築していると、開発者は認証フェーズでしばしば手間に直面します。手早く進めたいがために、Gmail なら gmail.modify、Google Drive なら drive といった広いアクセススコープを要求したくなります。これらのスコープは、ワークスペース全体に対する無制限の読み取り、書き込み、削除の権限を AI エージェントに与えてしまいます。

広すぎるスコープの危険性は、大規模言語モデルの非決定論的な性質を考えると明らかになります。顧客からのメールを処理する社内 AI ワークフローは、間接的なプロンプトインジェクションによって操作される可能性があります。ワークフローに書き込み権限がすべて与えられていれば、受信メールに潜ませた悪意あるペイロードがエージェントに指示を出し、社内の機密文書を外部アドレスへ転送させたり、Google Drive の重要なファイルを削除させたりできてしまいます。

最小権限の AI という原則を徹底すれば、組織はこうしたリスクを軽減できます。エージェントを読み取り専用のアクセスに制限する、あるいは書き込みを特定の重要度の低いディレクトリのみに限定することで、たとえエージェントが侵害されても壊滅的な被害には至りません。きめ細かい OAuth スコープは強固なセキュリティ境界として機能し、LLM の確率的な振る舞いと、ID プロバイダーが強制する決定論的なアクセス制御とを切り離します。

最小権限を前提とした OAuth アプリケーションの設計

きめ細かい OAuth スコープを実装する第一歩は、ID プロバイダーを正しく設定することです。Google Cloud Platform であれば、OAuth 同意画面を作成し、アプリケーションが必要とするスコープを正確に定義することを意味します。

フルアクセスを要求するのではなく、そのワークフローに最低限必要な権限を見極めてください。たとえば AI エージェントが受信したサポートチケットを分類するためにメールのメタデータを読むだけなら、gmail.metadata スコープを要求します。特定のメール本文を読む必要がある場合は gmail.readonly を使います。gmail.modify は、ワークフローがメールの送信や変更を明示的に必要とする場合を除いて決して要求してはいけません。必要な場合でも、読み取り操作と書き込み操作を別々のエージェントに分け、それぞれ異なる認証情報を持たせることを検討してください。

OAuth 同意画面を設定する際は、要求する各スコープについて明確な正当性を記載してください。これはアプリケーション審査の要件であるだけでなく、セキュリティチームに向けた社内ドキュメントとしても機能します。AI ワークフローがなぜその資源へのアクセスを必要とするのか、そして実運用でそのアクセスをどう制限するのかを明記しましょう。

例:Python でのスコープ設定

Python で OAuth フローを初期化する際は、AI ワークフローが必要とするきめ細かいスコープを明示的に渡します。google-auth ライブラリを使った実装は次のようになります。

from google_auth_oauthlib.flow import InstalledAppFlow
from google.oauth2.credentials import Credentials

# 粒度の細かい読み取り専用スコープを定義する
SCOPES = [
    'gmail.readonly',
    'calendar.events.readonly'
]

def authenticate_agent() -> Credentials:
    flow = InstalledAppFlow.from_client_secrets_file(
        'credentials.json', SCOPES)
    creds = flow.run_local_server(port=0)
    return creds

この設定により、得られるアクセストークンはメールとカレンダーの予定を読むことしかできなくなります。AI ワークフローの認可スコープが書き込みや削除の操作を試みても、Google API は HTTP 403 Forbidden エラーで拒否します。

LangGraph による安全なワークフローの実装

LangGraph は、状態を保持するマルチアクター型の AI ワークフローを構築するための強力なフレームワークです。LangGraph アプリケーションに OAuth の認証情報を組み込む際は、アクセストークンを安全に管理し、それを必要とする特定のノードにのみ渡すことが極めて重要です。

アクセストークンを全ノードから参照できるグローバルな状態変数に格納するのではなく、API を呼び出す担当のツールやノードへ明示的に注入してください。こうすることで、悪意あるプロンプトがワークフローの別の箇所にトークンを悪用させる指示を出すことを防げます。

状態管理と認証情報の注入

生の認証情報を露出させずにワークフローの進行状況を追跡する LangGraph の状態を定義します。認証情報は実行時にセキュアなシークレットマネージャーや環境変数から取得し、実行コンテキストへ直接渡すべきです。

import os
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

class WorkflowState(TypedDict):
    query: str
    email_data: str
    analysis: str

def fetch_emails(state: WorkflowState) -> WorkflowState:
    # 認証情報は使用する箇所で安全に取得する
    creds = Credentials.from_authorized_user_file('token.json')
    service = build('gmail', 'v1', credentials=creds)

    # 読み取り専用の API 呼び出しを実行する
    results = service.users().messages().list(userId='me', maxResults=5).execute()
    messages = results.get('messages', [])

    # 認証情報ではなく、必要なデータだけを処理して返す
    return {"email_data": f"Fetched {len(messages)} messages."}

def analyze_content(state: WorkflowState) -> WorkflowState:
    # このノードは LLM による分析を行い、OAuth 認証情報にはアクセスしない
    analysis = f"Analysis of: {state['email_data']}"
    return {"analysis": analysis}

# LangGraph のワークフローを構築する
workflow = StateGraph(WorkflowState)
workflow.add_node("fetch", fetch_emails)
workflow.add_node("analyze", analyze_content)
workflow.set_entry_point("fetch")
workflow.add_edge("fetch", "analyze")
workflow.add_edge("analyze", END)
app = workflow.compile()

このアーキテクチャでは、fetch_emails ノードが OAuth 認証情報を安全に扱い、外部 API とやり取りします。確率的な言語モデルを動かす analyze_content ノードは、抽出済みのデータしか受け取りません。この職掌分離により、アプリケーション内部でも最小権限の AI という原則が徹底されます。

認証失敗とトークン失効への対応

堅牢な社内 AI ワークフローは、トークンの失効や権限の取り消しを含む認証失敗を適切に処理しなければなりません。OAuth トークンが失効した際、アプリケーションはワークフローを中断させることも手作業を求めることもなく、自動で更新できる必要があります。

google-auth ライブラリには、失効したトークンを更新する仕組みが標準で用意されています。API を呼び出す前にトークンの状態を確認し、必要であれば更新するようにしてください。

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import os

def get_valid_credentials() -> Credentials:
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json')

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token is not None:
            creds.refresh(Request())
            # 更新したトークンを次回のために保存する
            with open('token.json', 'w') as token_file:
                token_file.write(creds.to_json())
        else:
            raise PermissionError("Valid credentials are not available. Manual authentication required.")

    return creds

ユーザーがアクセスを取り消した、あるいはリフレッシュトークン自体が失効したなどの理由でトークンを更新できない場合、ワークフローは安全に停止しなければなりません。クラッシュさせるのではなく、AI ワークフローは例外を捕捉し、失敗を安全に記録したうえで、システム管理者に通知すべきです。トークンの詳細やスタックトレースを含む生の API エラーメッセージを、エンドユーザーに露出させては決していけません。

失敗処理のアーキテクチャ

失敗処理の仕組みを設計する際は、次のベストプラクティスを検討してください。

  1. サーキットブレーカーの実装:認証サービスが停止している、または継続的にエラーを返している場合は、API への負荷集中とアカウントのロックを防ぐため、ワークフローを一時的に停止します。
  2. アラートと監視:ワークフローを Datadog や Prometheus といった社内の監視ツールと連携させます。認証の失敗、異常に多い API 利用、認可されていないスコープへのアクセス試行に対してアラートを設定しましょう。
  3. フォールバックの仕組み:認証エラーで主系の AI ワークフローが失敗した場合に備え、決定論的なフォールバック経路を定義します。たとえばエージェントが新着メールを取得できないなら、キャッシュ済みのデータの分析に切り替えるか、人間のオペレーターにタスクをエスカレーションします。

スコープ遵守の監査と検証

ワークフローのデプロイは出発点にすぎません。AI エージェントが定められたセキュリティ境界の内側で動作し続けていることを確かめるには、継続的な監査が必要です。ID プロバイダーの監査ログは、スコープの遵守状況を検証するうえで信頼できる情報源となります。

Google Workspace では、管理者は管理コンソールから OAuth アプリケーションの活動を監視できます。これらのログを定期的に確認し、AI ワークフローの認可スコープが、認可された資源にのみアクセスしていることを検証してください。想定外の API 呼び出し、通常の業務時間外のアクセス試行、データ取得量の急増といった異常がないか注意しましょう。

検証の自動化パイプライン

このプロセスを自動化するには、スコープの検証を継続的インテグレーションおよびデプロイ(CI/CD)のパイプラインに組み込みます。AI ワークフローを模擬し、定義したきめ細かい OAuth スコープだけを要求して使っていることを検証する統合テストを書きましょう。

import unittest
from unittest.mock import patch, MagicMock

class TestWorkflowAuthentication(unittest.TestCase):

    @patch('google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file')
    def test_granular_scopes_requested(self, mock_flow):
        # 期待されるスコープを定義する
        expected_scopes = ['gmail.readonly']

        # 認証関数を実行する
        from workflow.auth import authenticate_agent
        authenticate_agent()

        # 正しいスコープでフローが初期化されたことを検証する
        mock_flow.assert_called_with('credentials.json', expected_scopes)

if __name__ == '__main__':
    unittest.main()

アプリケーションのコードとデプロイパイプラインの両方でスコープの制限を強制すれば、強固なセキュリティ体制を保ったまま、組織は社内 AI ワークフローを安心して拡大できます。

きめ細かい OAuth スコープの実装でよくある失敗

きめ細かい OAuth スコープを実装しようとしていても、AI ワークフローのセキュリティを損なう典型的な失敗がいくつかあります。

最も多い誤りは、開発の初期段階でスコープを過剰に付与し、本番へデプロイする前に制限し直すのを忘れてしまうことです。開発者は連携の不具合を調べるために広いスコープを使い、後で締めるつもりでいます。この技術的負債は、放置すれば重大な脆弱性になります。常に必要最小限のスコープから始め、どうしても必要になったときにだけ段階的に権限を追加してください。

もうひとつの失敗は、アプリケーションのアーキテクチャ内で認証情報を適切に隔離できていないことです。生のアクセストークンをワークフローの状態全体に流してしまうと、侵害されたどのノードからもそれを悪用できてしまいます。先に示したとおり、認証情報は使用する箇所でのみ注入し、ワークフローの他の部分には必要なデータだけを返しましょう。

最後に、スコープの強制をクライアント側だけに頼るのは致命的な失敗です。アクセス制御の最終的な権限は ID プロバイダーにあります。AI エージェントにデータを読むだけと指示していても、背後の OAuth トークンが書き込み権限を与えているなら、そのアプリケーションは依然として脆弱です。スコープは必ず ID プロバイダー側で明示的に設定してください。

トークン管理と安全な保管の応用

きめ細かい OAuth スコープを要求するだけでなく、その結果得られるアクセストークンとリフレッシュトークンを安全に保管・管理することが、社内 AI ワークフローの健全性を保つうえで決定的に重要です。トークンを平文ファイルやアプリケーションのソースコードに直接置くことは、重大なセキュリティ脆弱性です。

クラウドのシークレットマネージャーの活用

本番環境では、AWS Secrets Manager、Google Cloud Secret Manager、HashiCorp Vault といった専用のシークレット管理サービスでトークンを管理すべきです。これらのサービスは、暗号化された保管、アクセスログ、自動ローテーションの機能を提供します。

AI ワークフローの起動時には、実行環境のアイデンティティ(AWS の IAM ロールや GCP の Workload Identity など)を使ってシークレットマネージャーへ安全に認証し、必要な OAuth トークンを取得すべきです。このアプローチにより、認証情報をハードコードしたり、機密ファイルをアプリケーションコードと一緒に配布したりする必要がなくなります。

from google.cloud import secretmanager
import json
from google.oauth2.credentials import Credentials

def get_credentials_from_secret_manager(project_id: str, secret_id: str, version_id: str = "latest") -> Credentials:
    client = secretmanager.SecretManagerServiceClient()
    name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
    response = client.access_secret_version(request={"name": name})
    payload = response.payload.data.decode("UTF-8")
    token_data = json.loads(payload)
    return Credentials.from_authorized_user_info(token_data)

このパターンにより、AI ワークフローの認可スコープは保管時と転送時の暗号化で保護されます。さらに、セキュリティチームが認証情報へのアクセスを一元的に監視・監査できるようになり、不正利用に対する防御層がもう一枚加わります。

トークンローテーションの実装

きめ細かいスコープと安全な保管を実現していても、巧妙な攻撃や内部不正によって OAuth トークンが侵害される可能性はあります。このリスクを抑えるため、トークンローテーションを自動化する方針を導入してください。

リフレッシュトークンは長期間有効にできますが、アクセストークンは通常 1 時間以内に失効します。アプリケーションは新しいアクセストークンを継続的に要求しなければなりません。ただし、リフレッシュトークン自体も定期的にローテーションすべきです。ID プロバイダーによっては、アクセストークンを更新するたびに新しいリフレッシュトークンを発行し、古いリフレッシュトークンを無効化する、リフレッシュトークンの自動ローテーションに対応しています。

エンジニアリングチームが次に取るべき行動

既存の社内 AI ワークフローを見直し、現在付与されている OAuth スコープを監査してください。Gmail や Google Drive のような機密性の高いサービスにフルアクセス権限を持つエージェントがないか特定しましょう。そうしたワークフローを、可能な限り読み取り専用から始めて、厳密にきめ細かい OAuth スコープへ移行させてください。影響範囲を最小化することが、悪意あるプロンプトインジェクションと意図しない AI の誤動作の双方から、企業データを守ることにつながります。

参考文献

  • Seer AI Repository:AI ツール向けの安全なアーキテクチャパターンと、きめ細かい OAuth の実装例を示しています。
  • LangGraph Documentation:状態を保持するマルチアクター型 AI ワークフローの構築と、実行状態を安全に管理する方法を詳解しています。
  • Google Workspace OAuth Guide:OAuth 同意画面の設定とスコープ選択に関する公式ドキュメントです。

Fire In Belly について:エストニア・タリン発の独立系シニアエンジニアリングです。本記事で紹介した最小権限のガードレールを備えたカスタム社内ツールと AI ワークフローを、公開固定価格で設計・構築しています。次のプロジェクトのご相談は、お打ち合わせのご予約からどうぞ。