【実務・中級編】 APIのセキュリティ:CORS(Cross-Origin Resource Sharing)のプリフライトリクエスト – Web APIアーキテクチャ・データ連携実践ガイド

CORSプリフライトの深淵:なぜブラウザは「勝手に」OPTIONSを投げるのか

現場でよくある光景だ。「APIが動かない、ブラウザのコンソールには『CORS error』と出ている」。若手エンジニアは慌ててサーバー側の Access-Control-Allow-Origin: * を設定し、とりあえず動いたことにする。だが、少し待ってほしい。それがセキュリティホールへの入り口だとしたら?

今日は、Web API開発において避けては通れない、CORS(Cross-Origin Resource Sharing)の「プリフライトリクエスト」について、現場のインフラ屋の視点から紐解いていこう。

—

1. プリフライトリクエストは「安全確認」の儀式

ブラウザはなぜ、本番のリクエストを送る前に OPTIONS メソッドを投げるのか。RFC 6454およびFetch仕様で規定されたこの挙動は、「ブラウザが持つセキュリティの防波堤」だ。

例えば、あるサイトがあなたの認証情報(CookieやAuthorizationヘッダー)を勝手に使い、全く別のドメインのAPIを叩いてデータを盗み出そうとしたらどうなるか。これを防ぐために、ブラウザは「これから送るリクエストの内容(メソッドやカスタムヘッダー)を許可しますか?」とサーバーに事前確認を行う。これがプリフライトリクエストだ。

通信フローのリアル

1. クライアント(ブラウザ): 「POSTでJSONを送りたいんだけど、許可する?」という OPTIONS リクエストを送信。
2. サーバー: 「OK。POSTなら許可するよ。あとヘッダーはこれとこれを使ってくれ」とヘッダーを返す。
3. クライアント: 安全を確認できたので、ようやく本番の POST リクエストを送出。

この「事前確認」が失敗すると、ブラウザは「セキュリティ上の危険がある」と判断し、本番リクエストを止めてしまう。これがCORSエラーの正体だ。

—

2. インフラ・API設計者が守るべき「ヘッダーの鉄則」

プリフライトが成功するために、サーバー側は以下のHTTPヘッダーを適切に返す必要がある。

| ヘッダー名 | 役割 |
| :— | :— |
| Access-Control-Allow-Origin | 許可するドメイン(*は危険。可能な限りドメインを指定せよ) |
| Access-Control-Allow-Methods | 許可するHTTPメソッド(GET, POST, PUT, DELETE等) |
| Access-Control-Allow-Headers | 許可するカスタムヘッダー(Authorization, Content-Type等) |
| Access-Control-Max-Age | プリフライト結果のキャッシュ有効期間(秒) |

特に Access-Control-Max-Age は重要だ。これがないと、ブラウザはリクエストのたびに OPTIONS を投げることになり、APIサーバーの負荷とレイテンシを無駄に増大させることになる。

—

3. 実践:Nginxでの安全な設定例

インフラ屋として推奨するのは、アプリケーションコードで制御するよりも、NginxやApacheといったWebサーバー(あるいはAPIゲートウェイ)で一括管理する方法だ。

# Nginxの設定例
location /api/ {
    # 許可するドメインを明示的に指定(ワイルドカードは避けるのが鉄則)
    add_header 'Access-Control-Allow-Origin' 'https://www.example.com';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
    
    # プリフライト結果を24時間キャッシュする(パフォーマンス最適化)
    add_header 'Access-Control-Max-Age' 86400;

    # OPTIONSメソッドに対する即時レスポンス
    if ($request_method = 'OPTIONS') {
        return 204;
    }
    
    # ... 以下、バックエンドへのプロキシ設定 ...
}

—

4. 開発時のデバッグ:curlで挙動を追いかける

「ブラウザだとエラーになるが、原因がわからない」という時は、curlを使って強制的にプリフライトを再現してみよう。

# プリフライトリクエストをシミュレート
curl -I -X OPTIONS \
  -H "Origin: https://www.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  https://api.example.com/data

このコマンドのレスポンスに Access-Control-Allow-Origin が含まれていなければ、サーバー側の設定漏れだ。逆に、ヘッダーがあるのにエラーになる場合は、許可されているメソッドやヘッダーと、実際に送信しようとしている内容が一致していない可能性が高い。

—

最後に:CORSは「敵」ではなく「盾」

若手のうちは、CORSエラーを「邪魔な制約」と感じるかもしれない。だが、これはインターネットというオープンな空間で、あなたのユーザーのプライベートな情報を守るための不可欠な仕組みだ。

「とりあえず * にして動かそう」という誘惑に負けず、誰が、どのドメインから、何をするのかを明確に定義する。その丁寧なAPI設計こそが、堅牢なシステムを支えるエンジニアの矜持だ。

次回のブログでは、このCORSの裏側にある「Cookieの送受信(Access-Control-Allow-Credentials)」の罠について深掘りしようと思う。現場のトラブルシューティングで学んだ、汗臭い知見をまた共有する。それでは、また。

コメント

タイトルとURLをコピーしました