プリフライトリクエスト完全ガイド:CORSの仕組みと実装・デバッグ・運用まで

プリフライトリクエストとは何か(概要)

プリフライトリクエスト(preflight request)は、ブラウザが実行する「CORS(Cross-Origin Resource Sharing:クロスオリジンリソース共有)」の一連の安全確認プロセスの一部です。あるオリジン(スキーム+ホスト+ポート)から別のオリジンへリソース要求を行う際、リクエストが「安全」(いわゆる「シンプルリクエスト」)に該当しない場合、ブラウザは実際のリクエストを送る前に、まずサーバーへ HTTP の OPTIONS メソッドで“確認”を行います。これがプリフライトリクエストです。

なぜプリフライトが必要か(目的)

  • 安全性の確保:意図しない副作用を伴う HTTP メソッド(PUT/DELETE/PATCH など)やカスタムヘッダーが悪用されると、ユーザーの状態変更や機密情報漏洩につながる可能性があります。プリフライトはサーバー側がそのリクエストを受け付けるか明示的に応答させることで、リスクを軽減します。
  • サーバー側の許可確認:サーバーがどのオリジンやメソッド、ヘッダーを許容するかを事前に確認できるため、ブラウザは適切にリクエストを進めるか中止するか判断できます。

いつプリフライトが発生するか(トリガー)

プリフライトはブラウザが自動的に判断して行います。代表的なトリガーは以下:

  • HTTP メソッドが GET・HEAD・POST のいずれでもない(例:PUT、DELETE、PATCH、OPTIONS — ただしプリフライト自体は OPTIONS)
  • リクエストにカスタムヘッダー(例:X-My-Header)を含めている場合
  • Content-Type が「シンプル」なもの(text/plain、multipart/form-data、application/x-www-form-urlencoded)ではない(例:application/json はプリフライトを誘発)
  • Fetch API の mode や credentials の組み合わせなど、特定の属性が影響する場合

注意:実際の「シンプル」ヘッダー/メソッドの判定は仕様に定義されており、Accept や Accept-Language といった特定のヘッダーはサーフェス上シンプルに見えて許容されます。詳細は仕様やブラウザ実装に依存するため、MDN 等で確認することを推奨します。

プリフライトの仕組み(リクエストとレスポンス)

プリフライトは典型的には次のように行われます。

  • ブラウザ → サーバー(OPTIONS メソッド)
    • ヘッダー:Origin(要求元オリジン)
    • ヘッダー:Access-Control-Request-Method(本来送ろうとしている HTTP メソッド)
    • ヘッダー:Access-Control-Request-Headers(本来送ろうとしているカスタムヘッダーのリスト)
  • サーバー → ブラウザ(OPTIONS に対する応答)
    • 必須ではないが通常は 200 や 204 などのステータス
    • レスポンスヘッダー例:
      • Access-Control-Allow-Origin: または *
      • Access-Control-Allow-Methods: GET, POST, PUT, DELETE, ...
      • Access-Control-Allow-Headers: X-My-Header, Content-Type, ...(プリフライトで問い合わせられたヘッダーを含める)
      • Access-Control-Allow-Credentials: true(クッキー等の認証情報を許可する場合)
      • Access-Control-Max-Age: 600(プリフライト結果のキャッシュ有効期間 秒)

ブラウザはサーバーのレスポンスを確認し、許可されていれば本来のリクエストを送信します。許可されていなければブラウザはリクエストをブロックし、コンソールに CORS エラーを出します(サーバー側でリクエスト自体をブロックしているわけではなく、ブラウザがブロックします)。

よくある落とし穴と注意点

  • Access-Control-Allow-Origin と Access-Control-Allow-Credentials の組合せ:クレデンシャル(Cookie や Authorization ヘッダー)を許容する場合、Access-Control-Allow-Origin にワイルドカード「*」は使用できません。必ず具体的なオリジンを返す必要があります。
  • プリフライトに認証を要求すると失敗する:プリフライト(OPTIONS)に対しサーバー側が認証を求めて 401/403 を返すと、ブラウザは本来のリクエストを行いません。プリフライトは多くの場合、認証なしに受け付けるか、オリジンの許可だけ返す実装にします。
  • キャッシュの問題:Access-Control-Max-Age を設定していてもブラウザ側の上限や実装差があるため、長期的に頼りすぎない方がよいです。また、オリジンごとにキャッシュされる点にも注意。
  • Vary: Origin ヘッダーの追加:同一 URL に対して、Origin により Access-Control-Allow-Origin を変更している場合、CDN やプロキシは正しくキャッシュできないため、レスポンスに Vary: Origin を付けるべきです。
  • リダイレクトとプリフライト:プリフライトリクエストがリダイレクトされるとブラウザが失敗扱いにする実装があるため、プリフライトの応答は直接的であることが望ましいです。

パフォーマンス対策(プリフライトを減らす/最適化する)

  • 可能なら「シンプルリクエスト」にする:POST を使う場合は Content-Type を application/json にしない(多くの API は application/json を使うため、実務上は難しい)。
  • カスタムヘッダーを減らす:X- やカスタムヘッダーを増やさないことでプリフライトを避けられる場合がある。
  • Access-Control-Max-Age を適切に設定してブラウザ側でプリフライト結果をキャッシュする(ただしブラウザ実装やセキュリティ方針に依存)。
  • 必要ならばプロキシを立ててサーバーと同一オリジン扱いにする(同一オリジンであればもちろん CORS は不要)。

サーバー側での実装例(概念的)

代表的な実装ポイント:

  • OPTIONS を受けて適切な Access-Control-Allow-* ヘッダーを返す
  • プリフライトに認証を要求しない(あるいはオリジン許可だけ返す)
  • クレデンシャルを許可する場合はエコーされた Origin を返し、ワイルドカード禁止
  • Vary: Origin を付ける

簡単なコード例(Express.js の例):

const express = require('express');
const app = express();

app.options('*', (req, res) => {
  res.set('Access-Control-Allow-Origin', req.get('Origin') || '*');
  res.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With');
  res.set('Access-Control-Max-Age', '600');
  res.set('Vary', 'Origin');
  res.sendStatus(204);
});

// 他のルート...

デバッグの方法

  • ブラウザの開発者ツールの Network タブで OPTIONS(プリフライト)のリクエストとレスポンスを確認する
  • Console に出る「CORS」関連のエラーメッセージを読む(例えば「Access to fetch at '...' from origin '...' has been blocked by CORS policy」など)
  • レスポンスに必要な Access-Control-Allow-* ヘッダーが含まれているか、値が正しいか(Origin の扱い、Allow-Credentials と Allow-Origin の組合せ等)を確認

実務上の考慮点(セキュリティと運用)

  • 安易に Access-Control-Allow-Origin: * を設定しない(特に認証情報を扱う API では危険)
  • 必要なオリジンだけをホワイトリスト化して応答でエコーする運用が望ましい
  • プリフライトのログや頻度を監視して、不要なプリフライトが発生していないか確認する
  • 外部 CDN/プロキシ を使う場合、CORS ヘッダーの付与や Vary の扱いが正しいか確認する

最後に:プリフライトはブラウザの保護機構である

プリフライトリクエストは開発時にやや煩雑に思えますが、ブラウザがユーザーの安全を守るために導入した重要な仕組みです。サーバー側で正しく対応すれば問題なく動作しますし、必要ならば設計段階でオリジンを統一する、プロキシを導入する、ヘッダーやメソッドを見直すといった選択でプリフライト発生頻度を下げられます。特に認証付きリクエストを扱う場合は、Access-Control-Allow-Credentials と Allow-Origin の関係や Vary ヘッダーなどの細かい実装に注意してください。

参考文献