CORSヘッダー完全ガイド:同一生成元ポリシーの理解からプリフライト・実装・デバッグ・ベストプラクティスまで

CORSヘッダーとは — 基本の理解

CORS(Cross-Origin Resource Sharing)は、ブラウザが異なるオリジン(スキーム、ホスト、ポートが組み合わさったもの)間でリソースを安全に共有するための仕組みです。CORSヘッダーはサーバーがHTTPレスポンスに付与してブラウザに「このオリジンからのアクセスを許可するか」を伝える値群であり、主にブラウザ側の同一生成元ポリシー(Same-Origin Policy)と連携して動作します。

なぜCORSが必要か(同一生成元ポリシーとの関係)

同一生成元ポリシーは、悪意あるスクリプトがユーザーのセッション情報や機密データを別サイト経由で取得することを防ぐ重要なブラウザのセキュリティ機構です。しかし、現代のWebアプリケーションではAPIサーバーとフロントエンドが異なるオリジンにあることが一般的です。そこで必要となるのがCORSで、サーバー側が明示的に許可したオリジンからのリクエストだけをブラウザが受け入れるようにします。重要な点は、CORSはブラウザ側で強制されるルールであり、サーバー側のアクセス制御(認証・認可)そのものの代替ではないということです。

主要なCORS関連ヘッダー

  • Access-Control-Allow-Origin
    リクエスト元オリジンを指定します。値は特定のオリジン(例: https://example.com)かワイルドカード '*' です。注意:クッキーや認証情報を含むリクエスト(クレデンシャル付き)を許可する場合、'*' は使用できません。
  • Access-Control-Allow-Credentials
    ブラウザに対してクッキーやHTTP認証情報を含めることを許可するかを示します。値は "true" のみ有効。これが true のときは、Access-Control-Allow-Origin にワイルドカードは使えません。
  • Access-Control-Allow-Methods
    プリフライト(後述)に対するレスポンスで、許可するHTTPメソッド(GET, POST, PUT, DELETE, OPTIONS など)を列挙します。
  • Access-Control-Allow-Headers
    プリフライトで送られる Access-Control-Request-Headers に対して、実際に許可するカスタムヘッダー名を列挙します。例:Authorization, X-Requested-With など。
  • Access-Control-Expose-Headers
    ブラウザのJavaScriptから取得可能にするレスポンスヘッダーを列挙します。標準でアクセス可能なのは数個の簡易ヘッダーのみ(例: Cache-Control, Content-Language, Content-Type 等)なので、独自ヘッダーを参照したい場合に設定します。
  • Access-Control-Max-Age
    プリフライトレスポンスの結果をブラウザがキャッシュする秒数を指定します。長くするとプリフライト回数が減る反面、ポリシー変更の反映が遅れます。
  • Vary: Origin
    サーバーがリクエストの Origin ヘッダーに基づいて動的に Access-Control-Allow-Origin を返す場合、キャッシュの扱いのために Vary: Origin を付けるのが推奨されます。

プリフライトとシンプルリクエスト

ブラウザは「安全でない」可能性のあるクロスオリジンリクエストをする前に、OPTIONS メソッドで「プリフライト(preflight)」を行い、サーバーがそのメソッドやカスタムヘッダーを許可しているか確認します。プリフライトリクエストには以下が含まれます:Access-Control-Request-Method、Access-Control-Request-Headers、Origin。

一方で一部のリクエスト(例:GET/POST で Content-Type が text/plain, multipart/form-data, application/x-www-form-urlencoded の場合、かつ特定のヘッダーのみ使用)を「シンプルリクエスト」とし、プリフライトを行わないことがあります。とはいえ Authorization ヘッダーやカスタムヘッダーを使うとプリフライトが必要になります。

ブラウザ側の挙動と制約(重要ポイント)

  • サーバーが必要な Access-Control-* ヘッダーを返していなければ、ブラウザはレスポンスを JavaScript に渡さず、コンソールにCORSエラーを出します(ただし実際のリクエストはサーバーに到達している場合が多い)。
  • Access-Control-Allow-Origin にワイルドカードを設定している場合、ブラウザはレスポンスを許可しますが、クッキーを含めたリクエストは許可されません(Allow-Credentials が true の場合)。
  • Access-Control-Allow-Credentials: true を使うなら、必ず Access-Control-Allow-Origin に特定オリジンを設定し、ワイルドカードは避ける必要があります。
  • CORSはブラウザのポリシーであり、curl やサーバー間通信には適用されません。サーバー側でも適切に認証・認可を行う必要があります。

よくある設定ミスと危険なパターン

  • 任意のオリジンを反射して許可する(危険)
    リクエストの Origin をそのまま Access-Control-Allow-Origin にセットする実装が散見されます。入力検証なしにこれを行うと、悪意あるサイトからのリクエストを容易に許可してしまいます。必ずホワイトリスト照合を行ってください。
  • ワイルドカード + Credentials
    Allow-Credentials を true にしているのに Allow-Origin が '*' になっているとブラウザはクレデンシャル付きアクセスを拒否します。サーバーの意図と結果が一致しないことがあるので注意。
  • Vary ヘッダーを忘れる
    動的にオリジンを返す場合、CDNやキャッシュが誤ったレスポンスを返すことを防ぐため Vary: Origin を設定してください。

実装例(簡単なコードスニペット)

Node.js(Express)の最小例:

app.use((req, res, next) => {
  const origin = req.headers.origin;
  const whitelist = ['https://example.com', 'https://app.example.com'];
  if (whitelist.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
    res.setHeader('Access-Control-Max-Age', '600');
    return res.sendStatus(204);
  }
  next();
});

Nginx の例(簡易):

add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type' always;

パフォーマンスとプリフライトの最適化

プリフライトを頻繁に発生させるとパフォーマンスに影響します。Access-Control-Max-Age を適切に設定してプリフライト応答をキャッシュさせることでオーバーヘッドを削減できます。ただし、ポリシー変更が即時反映されないリスクとトレードオフになるため、値を決める際は慎重に。

CORSはセキュリティ対策のすべてではない

CORSはブラウザによるクロスオリジンアクセス制御の一部を担いますが、認証・認可・CSRF対策等は別途実装すべきです。例えば、クッキーに依存するAPIでは CSRF トークンを組み合わせる、あるいはトークンベース認証(Bearer トークン)にして同一生成元に依存しない設計にするなどの対策が必要です。

デバッグのポイント

  • ブラウザのDeveloper ToolsでNetworkタブを開き、該当リクエストとレスポンスヘッダーを確認する。
  • コンソールに表示されるCORS関連のエラーメッセージ(例:「Access to fetch at '...' from origin '...' has been blocked by CORS policy」)を手がかりにする。
  • プリフライトが204/200で返っているか、必要な Access-Control-Allow-* ヘッダーが揃っているかを確認。

まとめ — 実務での推奨事項

  • ワイルドカード(*)は本当に必要なときだけ使う。クッキーなどを扱うAPIでは避ける。
  • 動的にOriginを許可する場合はホワイトリストで検証し、Vary: Origin を付ける。
  • プリフライトの最適化(Access-Control-Max-Age)を適切に行いつつ、ポリシー変更の影響を考慮する。
  • CORSはブラウザ側の保護機構であり、サーバー側の認証・認可を疎かにしない。
  • 脆弱な実装(全てのOriginを反映する等)は攻撃ベクトルになるため避ける。

参考文献