CORSエラーを解決するためのプロキシスクリプト実装ガイド


 

CORSとは何か?

CORS(Cross-Origin Resource Sharing、オリジン間リソース共有)は、Webブラウザにおけるセキュリティ機能の一つで、異なるオリジン(ドメイン、プロトコル、ポートの組み合わせ)間でのリソース共有を制限します。Webアプリケーションが別のドメインのAPIやリソースにアクセスしようとすると、CORSポリシーによってブロックされることがあります。

CORSエラーが発生する状況

以下のような状況でCORSエラーが発生します:

  • Webサイト(例:https://example.com)のJavaScriptから別ドメインのAPI(例:https://api.another-domain.com)へ直接アクセスしようとした場合
  • 対象のAPIサーバーが適切なCORS関連のレスポンスヘッダーを返さない場合

ブラウザのコンソールには以下のようなエラーメッセージが表示されます:

Access to fetch at 'https://api.another-domain.com/data' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

プロキシを使用したCORSエラーの回避方法

CORSエラーを回避する最も一般的な方法の一つは、同一オリジン上のプロキシスクリプトを経由することです。

動作原理

  1. クライアントサイド(JavaScript)は外部APIに直接アクセスする代わりに、同じドメイン上にあるプロキシスクリプトにリクエストを送信
  2. サーバーサイドのプロキシスクリプトが外部APIにリクエストを転送
  3. プロキシスクリプトは外部APIからのレスポンスを受け取り、それをクライアントに返す

この方法では、ブラウザは同一オリジンのリソースにのみアクセスするため、CORSポリシーに違反しません。

PHP実装例

プロキシスクリプト(proxy.php)

<?php
// CORSヘッダーを設定
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *'); // すべてのオリジンからのアクセスを許可
header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); // 許可するHTTPメソッド
header('Access-Control-Allow-Headers: Content-Type, Authorization'); // 許可するヘッダー

// リクエストパラメータを取得
$api_url = isset($_GET['url']) ? $_GET['url'] : '';
$method = isset($_GET['method']) ? strtoupper($_GET['method']) : 'GET';

// APIのURLが指定されていない場合はエラー
if (empty($api_url)) {
    http_response_code(400);
    echo json_encode(['error' => 'API URL is required']);
    exit;
}

// cURLセッションを初期化
$ch = curl_init();

// リクエストメソッドに応じた設定
if ($method === 'POST') {
    curl_setopt($ch, CURLOPT_POST, true);
    // POSTデータがある場合はそれを設定
    if (isset($_POST) && !empty($_POST)) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST));
    } else {
        // JSON形式のリクエストボディがある場合
        $json_body = file_get_contents('php://input');
        if (!empty($json_body)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $json_body);
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        }
    }
} elseif ($method === 'GET' && isset($_GET) && !empty($_GET)) {
    // GETパラメータを転送(url以外のパラメータ)
    $params = $_GET;
    unset($params['url']);
    unset($params['method']);
    
    if (!empty($params)) {
        $api_url .= (strpos($api_url, '?') === false) ? '?' : '&';
        $api_url .= http_build_query($params);
    }
}

// cURLオプションを設定
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 本番環境では true に設定すべき
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; ProxyScript/1.0)');

// リクエストを実行
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

// エラーチェック
if (curl_errno($ch)) {
    http_response_code(500);
    echo json_encode(['error' => 'Curl error: ' . curl_error($ch)]);
    curl_close($ch);
    exit;
}

// レスポンスヘッダーを設定
http_response_code($http_code);
if ($content_type) {
    header('Content-Type: ' . $content_type);
}

// レスポンスを返す
echo $response;

// cURLセッションを閉じる
curl_close($ch);
?>

クライアントサイドJavaScriptでの使用例

// 外部APIに直接アクセスする代わりに、プロキシを経由
fetch('./proxy.php?url=https://api.example.com/data&method=GET')
  .then(response => response.json())
  .then(data => {
    console.log('Success:', data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

// POSTリクエストの例
fetch('./proxy.php?url=https://api.example.com/create&method=POST', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'Example', value: 123 }),
})
  .then(response => response.json())
  .then(data => {
    console.log('Success:', data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

jQueryでの実装例

$.ajax({
  url: './proxy.php',
  method: 'GET',
  data: {
    url: 'https://api.example.com/data',
    method: 'GET',
    // その他のAPIパラメータ
    param1: 'value1',
    param2: 'value2'
  },
  success: function(data) {
    console.log('データ取得成功:', data);
  },
  error: function(xhr, status, error) {
    console.error('エラー:', error);
  }
});

プロキシスクリプトのセキュリティ対策

プロキシスクリプトは便利ですが、セキュリティリスクも伴います。以下の対策を検討してください:

  1. アクセス先URLの制限
    • 信頼できるドメインのみにアクセスを許可
    • ホワイトリストによるドメイン制限を実装
// 許可するドメインのリスト
$allowed_domains = ['api.trusted-domain.com', 'data.example.org'];

// URLからドメインを抽出
$url_parts = parse_url($api_url);
$domain = isset($url_parts['host']) ? $url_parts['host'] : '';

// ドメインがホワイトリストにあるか確認
if (!in_array($domain, $allowed_domains)) {
    http_response_code(403);
    echo json_encode(['error' => 'Domain not allowed']);
    exit;
}
  1. レート制限の実装
    • 短時間に多数のリクエストを送信する攻撃を防ぐ
    • セッションやIPアドレスごとにリクエスト回数を制限
  2. リクエストの検証
    • 不正なパラメータやインジェクション攻撃を防ぐ
    • 入力値のバリデーションを徹底する

本番環境での考慮事項

  1. SSL証明書の検証を有効に
    • 本番環境では curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); に設定
    • 適切なCAバンドルを指定
  2. 適切なエラーハンドリング
    • 詳細なエラーメッセージは開発環境でのみ表示
    • 本番環境ではエラーの詳細を隠蔽
  3. ログ記録
    • 異常なアクセスパターンを検出するために適切なログを記録
    • 個人情報やセンシティブデータはログに記録しない

まとめ

プロキシスクリプトを使用することで、フロントエンドのJavaScriptからCORSポリシーを気にせずに外部APIにアクセスできるようになります。ただし、セキュリティリスクも考慮し、適切な対策を講じることが重要です。

この方法は特に以下のケースで有効です:

  • 外部APIがCORSヘッダーを提供していない場合
  • APIキーなどの機密情報をクライアントサイドに公開したくない場合
  • リクエストを集約して管理・監視したい場合

プロキシスクリプトは実装が比較的簡単で、多くのWeb開発環境で利用できる便利なソリューションです。

WEBプログム、WEBデザインなどの制作については、以下を御覧ください。

WEBプログム、WEBデザインなどの制作