🔑 OAuth 2.0

OAuth 2.0はAPIアクセスの認可フレームワークとして最も広く使われています。適切なグラントタイプの選択が重要です。

グラントタイプの選択

グラントタイプ ユースケース セキュリティレベル
Authorization Code + PKCE SPAやモバイルアプリ(推奨) 最高
Authorization Code サーバーサイドWebアプリ
Client Credentials サーバー間通信(M2M)
Implicit(非推奨) レガシーSPA
Resource Owner Password(非推奨) 高信頼ファーストパーティのみ
Implicit Grant は使用禁止

Implicit GrantはトークンがURLフラグメントに露出するため、現在は非推奨です。SPAでは Authorization Code + PKCE を使用してください。

Authorization Code + PKCE フロー

JavaScript PKCE チャレンジ生成
// 1. code_verifier をランダム生成
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 2. code_challenge を SHA-256 で生成
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 3. 認可リクエスト
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);

const authUrl = `https://auth.example.com/authorize?` +
  `response_type=code&` +
  `client_id=${clientId}&` +
  `redirect_uri=${redirectUri}&` +
  `code_challenge=${challenge}&` +
  `code_challenge_method=S256&` +
  `scope=read write`;

🎫 JWT(JSON Web Token)

JWTはステートレスな認証トークンとして広く使われますが、実装ミスがセキュリティホールに直結します。

JWTのセキュリティ注意点

✅ やるべきこと

  • アルゴリズムを明示的に指定(HS256 / RS256)
  • 有効期限(exp)を短く設定(15分以内)
  • issuer(iss)と audience(aud)を検証
  • 秘密鍵は十分な長さ(256bit以上)で生成
  • リフレッシュトークンにはローテーションを実装

❌ やってはいけないこと

  • JWTペイロードに機密情報を入れる(Base64は暗号化ではない)
  • alg: "none" を許可する
  • トークンをlocalStorageに保存(XSS脆弱)
  • トークンの失効管理をしない
  • RS256の公開鍵をHS256の秘密鍵として使用する

安全なJWT実装

JavaScript (Node.js) トークン発行と検証
const jwt = require('jsonwebtoken');

// --- トークン発行 ---
function issueTokens(user) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    {
      algorithm: 'HS256',
      expiresIn: '15m',
      issuer: 'api.example.com',
      audience: 'app.example.com',
    }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    process.env.REFRESH_SECRET,
    { algorithm: 'HS256', expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
}

// --- トークン検証 ---
function verifyAccessToken(token) {
  return jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],   // ← 必ず指定!
    issuer: 'api.example.com',
    audience: 'app.example.com',
  });
}

トークン保存場所の比較

保存場所 XSS耐性 CSRF耐性 推奨度
HttpOnly Cookie △(SameSite設定要) 推奨
メモリ(変数) ページ遷移で消失
localStorage ✕(XSSで窃取可能) 非推奨
sessionStorage ✕(XSSで窃取可能) 限定的
推奨: HttpOnly + Secure + SameSite Cookie

アクセストークンは HttpOnly、Secure、SameSite=Strict 属性を付けた Cookie に保存するのが最も安全です。これにより XSS 攻撃でのトークン窃取を防ぎます。

🗝 APIキー管理

APIキーはシンプルな認証手段ですが、適切に管理しないと重大なリスクになります。

APIキーのベストプラクティス

生成と保存

十分なエントロピーで生成(256bit以上)。ハッシュ化して保存し、プレーンテキストでDBに保持しない。

スコープ制限

キーごとに最小限のスコープ(読み取り専用、特定リソースのみ等)を付与。ワイルドカード権限は避ける。

ローテーション

定期的なキーローテーション(90日以内)。古いキーのグレースピリオド設定。漏洩時は即時無効化。

送信方法

ヘッダー(X-API-Key or Authorization)で送信。URLクエリパラメータには含めない(ログに残る)。

環境変数管理

キーはソースコードにハードコードせず、環境変数やシークレットマネージャーで管理。

監視とログ

APIキーの使用状況を監視。異常なパターン(大量リクエスト、未知のIPからのアクセス)を検出。

APIキー検証の実装例

JavaScript (Express) ハッシュ化されたAPIキー検証
const crypto = require('crypto');

// APIキーをSHA-256でハッシュ化して比較
async function validateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  // ハッシュ化してDB照合(タイミング攻撃対策のため timingSafeEqual は不要 - ハッシュ比較のため)
  const hashedKey = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  const keyRecord = await db.findApiKey(hashedKey);

  if (!keyRecord || keyRecord.revokedAt) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  // 有効期限チェック
  if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) {
    return res.status(403).json({ error: 'API key expired' });
  }

  req.apiClient = keyRecord;
  next();
}

🤖 LLM・AIエージェントのAPIアクセス保護

AIエージェントやLLMを活用したアプリケーションには、従来のAPIセキュリティに加えて、追加の認証・認可パターンが必要です。

モデル別スコープ

モデルのティアごとに異なるアクセスレベルを割り当てます(例: GPT-4は上位スコープが必要)。高コストまたは機密性の高いモデルの不正利用を防止します。

トークン予算の適用

アクセストークンやAPIキーのメタデータにトークン予算を埋め込みます。リクエスト単位・日単位の制限を適用し、Denial-of-Wallet攻撃を防止します。

プロンプトレベル認可

特定のプロンプトタイプ(例: コード生成、データ分析)の実行前にユーザー権限を確認します。プロンプトカテゴリをロールベースの権限にマッピングします。

マルチテナント分離

会話履歴、ファインチューニング済みモデル、RAGデータがテナントごとに分離されていることを保証します。名前空間の適用を伴うテナントスコープのAPIキーを使用します。

エージェントのID・アテステーション

AIエージェントに暗号学的なIDを割り当てます。エージェント間通信には署名付きJWTを使用します。ツールアクセスの付与前にエージェントのIDを検証します。

AIサービスのクレデンシャルローテーション

LLMプロバイダーのAPIキーローテーションを自動化します。エージェントセッションには短命トークンを使用します。エージェントの廃止時にはクレデンシャルを即時失効させます。

エージェントのツールアクセス認可

シナリオ 認証方式 必要なスコープ 承認
エージェントが公開データを読み取り API Key data:read 自動
エージェントがユーザーデータを変更 OAuth 2.0 (delegated) data:write ユーザーの同意が必要
エージェントが外部APIを呼び出し OAuth 2.0 + mTLS external:invoke Human-in-the-loop(人間による確認)
エージェントがコード/シェルを実行 Scoped JWT + Sandbox exec:sandbox 管理者承認 + 監査ログ

AIエージェント向けスコープ付きOAuthトークン(Python)

Python スコープ適用によるエージェントトークン発行
import jwt
import time
from datetime import datetime, timedelta

class AgentTokenIssuer:
    """Issues scoped, short-lived tokens for AI agents."""

    ALLOWED_SCOPES = {
        "reader": ["data:read"],
        "writer": ["data:read", "data:write"],
        "executor": ["data:read", "data:write", "exec:sandbox"],
    }

    def __init__(self, secret_key: str):
        self.secret_key = secret_key

    def issue_agent_token(
        self,
        agent_id: str,
        role: str,
        tenant_id: str,
        token_budget: int = 10000,
        ttl_minutes: int = 15,
    ) -> str:
        # ロールを検証しスコープを解決
        if role not in self.ALLOWED_SCOPES:
            raise ValueError(f"Invalid role: {role}")

        payload = {
            "sub": agent_id,
            "tenant": tenant_id,
            "scopes": self.ALLOWED_SCOPES[role],
            "token_budget": token_budget,
            "iat": datetime.utcnow(),
            "exp": datetime.utcnow() + timedelta(minutes=ttl_minutes),
            "type": "agent",
        }
        return jwt.encode(payload, self.secret_key, algorithm="HS256")

    def verify_tool_access(self, token: str, required_scope: str) -> dict:
        # エージェントトークンをデコードして検証
        payload = jwt.decode(
            token, self.secret_key, algorithms=["HS256"]
        )
        if required_scope not in payload["scopes"]:
            raise PermissionError(
                f"Agent {payload['sub']} lacks scope: {required_scope}"
            )
        return payload
OWASP関連リファレンス

関連: LLM06: Excessive AgencyASI01: Excessive AgencyASI03: Insecure Tool/Function Calling

認証方式の比較

方式 ステートレス 適用場面 セキュリティ上の注意
OAuth 2.0 + PKCE ユーザー認可が必要なAPI state/PKCE必須、トークン有効期限管理
JWT Bearer マイクロサービス間通信 alg指定必須、ペイロードに機密情報を入れない
APIキー サーバー間、外部連携 ローテーション・スコープ制限必須
mTLS 高セキュリティM2M通信 証明書管理の運用コスト高
セッションCookie 従来型Webアプリ CSRF対策必須、スケーラビリティに課題