コンテンツにスキップ

CORS 設定

Cross-Origin Resource Sharing (CORS) は、Web ブラウザのセキュリティ機能で、異なるオリジンからのリクエストを制御します。lambapi は CORS の設定と自動処理を簡単に行えます。

CORS とは

CORS は、Web ページが異なるドメイン、プロトコル、ポートからリソースにアクセスすることを許可する仕組みです。

同一オリジンポリシー

ブラウザはセキュリティのため、デフォルトで同一オリジンからのリクエストのみを許可します:

https://example.com/page → https://example.com/api ✅ (同一オリジン)
https://example.com/page → https://api.example.com/data ❌ (異なるサブドメイン)
https://example.com/page → http://example.com/api ❌ (異なるプロトコル)

CORS の仕組み

CORS は HTTP ヘッダーを使用して、クロスオリジンリクエストを許可します:

  1. Simple Request: 直接リクエストを送信
  2. Preflight Request: OPTIONS リクエストで事前確認

lambapi での CORS 設定

1. グローバル CORS 設定

すべてのエンドポイントに適用される設定:

from lambapi import API, create_lambda_handler

def create_app(event, context):
    app = API(event, context)

    # 基本的な CORS 有効化
    app.enable_cors()

    @app.get("/api/users")
    def get_users():
        return {"users": []}

    return app

2. 詳細な CORS 設定

def create_app(event, context):
    app = API(event, context)

    # 詳細な CORS 設定
    app.enable_cors(
        origins=["https://myapp.com", "https://admin.myapp.com"],
        methods=["GET", "POST", "PUT", "DELETE"],
        headers=["Content-Type", "Authorization", "X-API-Key"],
        allow_credentials=True,
        max_age=3600,  # プリフライトキャッシュ時間(秒)
        expose_headers=["X-Total-Count"]
    )

    @app.get("/api/data")
    def get_data():
        return {"data": "secure data"}

    return app

3. ルートレベル CORS 設定

個別のエンドポイントに異なる CORS 設定を適用:

from lambapi import create_cors_config

def create_app(event, context):
    app = API(event, context)

    # パブリック API(緩い設定)
    @app.get("/api/public", cors=True)
    def public_endpoint():
        return {"message": "Public data"}

    # 管理者 API(厳格な設定)
    admin_cors = create_cors_config(
        origins=["https://admin.myapp.com"],
        methods=["GET", "POST"],
        allow_credentials=True
    )

    @app.get("/api/admin", cors=admin_cors)
    def admin_endpoint():
        return {"message": "Admin data"}

    # CORS 無効のエンドポイント
    @app.get("/api/internal")
    def internal_endpoint():
        return {"message": "Internal only"}

    return app

CORS 設定オプション

origins(オリジン設定)

# すべてのオリジンを許可(開発用)
app.enable_cors(origins="*")

# 特定のオリジンのみ許可
app.enable_cors(origins="https://myapp.com")

# 複数のオリジンを許可
app.enable_cors(origins=[
    "https://myapp.com",
    "https://admin.myapp.com",
    "http://localhost:3000"  # 開発環境
])

# 正規表現パターン(実装で対応可能)
# app.enable_cors(origins=r"https://.*\.myapp\.com")

methods(HTTP メソッド)

# デフォルト設定
app.enable_cors(methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])

# 読み取り専用 API
app.enable_cors(methods=["GET", "OPTIONS"])

# フルアクセス
app.enable_cors(methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])

headers(許可ヘッダー)

# 基本的なヘッダー
app.enable_cors(headers=["Content-Type"])

# 認証付き API
app.enable_cors(headers=[
    "Content-Type",
    "Authorization",
    "X-API-Key",
    "X-Requested-With"
])

# カスタムヘッダー
app.enable_cors(headers=[
    "Content-Type",
    "Authorization",
    "X-Custom-Header",
    "X-Client-Version"
])

allow_credentials(認証情報)

# Cookie や Authorization ヘッダーを許可
app.enable_cors(allow_credentials=True)

# 注意: origins="*" と allow_credentials=True は同時に使用不可
app.enable_cors(
    origins=["https://myapp.com"],  # 具体的なオリジンを指定
    allow_credentials=True
)

max_age(キャッシュ時間)

# プリフライトリクエストの結果を 1 時間キャッシュ
app.enable_cors(max_age=3600)

# キャッシュ無し(毎回プリフライト)
app.enable_cors(max_age=0)

# 長期キャッシュ(1 日)
app.enable_cors(max_age=86400)

expose_headers(公開ヘッダー)

# JavaScript からアクセス可能なレスポンスヘッダー
app.enable_cors(expose_headers=[
    "X-Total-Count",
    "X-Page-Count",
    "X-Rate-Limit-Remaining"
])

プリフライトリクエストの自動処理

lambapi は OPTIONS リクエスト(プリフライト)を自動的に処理します:

def create_app(event, context):
    app = API(event, context)

    app.enable_cors(
        origins=["https://myapp.com"],
        methods=["GET", "POST", "PUT", "DELETE"],
        headers=["Content-Type", "Authorization"]
    )

    @app.post("/api/users")
    def create_user(request):
        return {"message": "User created"}

    # OPTIONS /api/users が自動的に処理される
    # ブラウザが POST リクエスト前に自動送信

    return app

環境別 CORS 設定

開発環境

import os

def create_app(event, context):
    app = API(event, context)

    if os.getenv("ENVIRONMENT") == "development":
        # 開発環境では緩い設定
        app.enable_cors(
            origins="*",
            methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
            headers=["*"]
        )
    else:
        # 本番環境では厳格な設定
        app.enable_cors(
            origins=["https://myapp.com"],
            methods=["GET", "POST", "PUT", "DELETE"],
            headers=["Content-Type", "Authorization"],
            allow_credentials=True
        )

    return app

設定ファイルからの読み込み

import json

def load_cors_config():
    """設定ファイルから CORS 設定を読み込み"""
    try:
        with open("cors_config.json", "r") as f:
            return json.load(f)
    except FileNotFoundError:
        return {
            "origins": "*",
            "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
            "headers": ["Content-Type"]
        }

def create_app(event, context):
    app = API(event, context)

    cors_config = load_cors_config()
    app.enable_cors(**cors_config)

    return app
cors_config.json
{
    "origins": ["https://myapp.com", "https://admin.myapp.com"],
    "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    "headers": ["Content-Type", "Authorization", "X-API-Key"],
    "allow_credentials": true,
    "max_age": 3600
}

セキュリティ考慮事項

1. origins の設定

# ❌ 本番環境では避ける
app.enable_cors(origins="*", allow_credentials=True)

# ✅ 具体的なオリジンを指定
app.enable_cors(
    origins=["https://myapp.com"],
    allow_credentials=True
)

# ✅ 環境変数から読み込み
import os
app.enable_cors(
    origins=os.getenv("ALLOWED_ORIGINS", "").split(","),
    allow_credentials=True
)

2. 機密データの保護

def create_app(event, context):
    app = API(event, context)

    # パブリック API は緩い設定
    public_cors = create_cors_config(origins="*")

    @app.get("/api/public/status", cors=public_cors)
    def public_status():
        return {"status": "ok"}

    # プライベート API は厳格な設定
    private_cors = create_cors_config(
        origins=["https://admin.myapp.com"],
        allow_credentials=True
    )

    @app.get("/api/private/users", cors=private_cors)
    def private_users():
        return {"users": ["sensitive", "data"]}

    return app

トラブルシューティング

1. CORS エラーの確認

// フロントエンド(JavaScript)での確認
fetch('https://api.example.com/users', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
    },
    credentials: 'include'  // allow_credentials=True が必要
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
    // CORS エラーの場合、詳細なエラー情報は見えない
    console.error('CORS error:', error);
});

2. デバッグ用ログ

def create_app(event, context):
    app = API(event, context)

    def cors_debug_middleware(request, response):
        # CORS ヘッダーをログ出力
        if isinstance(response, Response):
            cors_headers = {k: v for k, v in response.headers.items() 
                          if k.startswith('Access-Control-')}
            print(f"CORS headers: {cors_headers}")
        return response

    app.add_middleware(cors_debug_middleware)
    app.enable_cors(origins=["https://myapp.com"])

    return app

3. よくある問題と解決策

問題 原因 解決策
Access-Control-Allow-Origin エラー オリジンが許可されていない origins 設定を確認
Access-Control-Allow-Methods エラー HTTP メソッドが許可されていない methods 設定に追加
Access-Control-Allow-Headers エラー ヘッダーが許可されていない headers 設定に追加
認証情報が送信されない allow_credentials が false allow_credentials=True に設定
プリフライトで失敗 OPTIONS が処理されていない lambapi が自動処理(確認が必要)

実際のフロントエンド連携例

React アプリケーション

// React での API 呼び出し
const apiCall = async () => {
    try {
        const response = await fetch('https://api.example.com/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}`
            },
            credentials: 'include',
            body: JSON.stringify({
                name: 'John Doe',
                email: 'john@example.com'
            })
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        console.log('Success:', data);
    } catch (error) {
        console.error('Error:', error);
    }
};

対応する lambapi 設定

def create_app(event, context):
    app = API(event, context)

    # React アプリに対応する CORS 設定
    app.enable_cors(
        origins=["https://myapp.com", "http://localhost:3000"],
        methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        headers=["Content-Type", "Authorization"],
        allow_credentials=True,
        max_age=3600
    )

    @app.post("/users")
    def create_user(request):
        user_data = request.json()
        return {"message": "User created", "user": user_data}

    return app

CORS の適切な設定により、セキュリティを保ちながらフロントエンドアプリケーションとのスムーズな連携が可能になります。