こんにちは!ネットワークの深淵とWebの裏側を愛するインフラエンジニアです。
日々の開発やインフラ構築で、フロントエンドとバックエンドを切り分けて開発していると、突然ブラウザのコンソール画面に「CORSエラーだ!」という赤字の警告が出て頭を抱えた経験はありませんか? 「ちゃんとしたAPIを作ったはずなのに、なぜブラウザは怒っているんだろう…」と、最初のうちは途方に暮れてしまいますよね。
今回は、このWeb開発の大きな壁の一つであるCORS(Cross-Origin Resource Sharing)のプリフライトリクエストについて、難しい専門用語の裏側にある「本当の仕組み」を、身近な例えを交えながら一歩ずつ優しく解きほぐしていきましょう!
—
そもそも「CORS」ってどんな仕組み?
一歩ずつ理解していきましょう!まずは、私たちが普段使っているWebブラウザが、なぜそんなにも厳格にセキュリティを気にしているのかという背景からお話しします。
Webブラウザは、私たちが意図しないところで悪意のあるサイトが勝手に別のサイト(例えば銀行の口座など)にアクセスしてデータを盗み見ないよう、「同一生成元ポリシー(Same-Origin Policy)」という鉄の掟を持っています。これは簡単に言うと、「自分が今いる場所(オリジン:ざっくり言うとURLのドメインやポート番号)と同じ場所としか通信しちゃダメ!」という強力なガードマンです。
しかし、現代のWebアプリケーションは、フロントエンド(ReactやVue.jsなど)を https://example.com で動かし、APIサーバーは別の場所(例えば https://api.example.com)に置くというスタイルが主流です。「違う場所にあるサーバーと通信したい!」という正当な理由があるわけですね。
そこで登場するのが CORS(Cross-Origin Resource Sharing) です。これは、「ちゃんと許可を取ってくれた相手なら、違う場所にあるサーバーと通信してもいいよ」という、ブラウザのガードマンに対する例外許可証の仕組みなんですよ。
—
郵便配達で例える「プリフライトリクエスト」の流れ
では、今回の本丸である「プリフライトリクエスト(Preflight Request)」について見ていきましょう。
「プリフライト(Preflight)」という言葉は、飛行機が離陸する前に行う「点検・安全確認」から来ています。CORSの世界でも、ブラウザは本番のデータ通信を行う前に、必ず「事前の安全確認(お伺い)」を行っているのです。
これを身近な例えで考えてみましょう。
あなたは今、全く知らない他人の家(別ドメインのAPIサーバー)に、大切な荷物を届けに行こうとしています。
いきなり大きな荷物を玄関のドアにドカンと置いたら、家主はびっくりして警察を呼んでしまうかもしれませんよね。
そこで、礼儀正しい郵便配達員(Webブラウザ)は、次のような手順を踏みます。
1. 事前のお伺い(OPTIONSメソッド)
いきなり荷物を送る代わりに、まずドアをノックして「こういう荷物を、こういう内容で送りたいんですが、受け取ってもらえますか?」と家主に尋ねます。これがHTTPの OPTIONS メソッドを使ったプリフライトリクエストです。
2. 家主の判断(レスポンスヘッダー)
家主は「ああ、その人からの荷物なら受け取っていいよ(Access-Control-Allow-Origin)」とか、「この条件ならOKだよ」と返事をします。
3. 本番の荷物配達(GETやPOSTメソッド)
無事に許可が出たことを確認してから、ブラウザはやっと本番のデータ(JSONなど)をサーバーに送信します。
つまり、プリフライトリクエストとは、「ブラウザが私たちの代わりに、勝手にサーバーへ安全確認のジャブを打ってくれている状態」なのです。
—
サーバー側で設定すべき「許可証」の正体
この安全確認をスムーズに通過するためには、APIサーバー側で「私はこういうアクセスを許可しますよ」という看板(HTTPレスポンスヘッダー)を掲げておく必要があります。
実務でよく使われる代表的な設定項目をみてみましょう。
Access-Control-Allow-Origin: どのドメインからのアクセスを許可するか(例:https://example.comや、すべてを許可する*)Access-Control-Allow-Methods: どのHTTPメソッドを許可するか(例:GET,POST,OPTIONSなど)Access-Control-Allow-Headers: リクエストにどんなヘッダーが含まれていてよいか(例:Content-Type,Authorizationなど)
例えば、NginxというWebサーバーを使って、すべてのドメインからの POST リクエストと特定の認証ヘッダーを許可したい場合、設定ファイルには次のように記述します。
# Nginxの設定例:CORSの許可ヘッダーを付与する
location /api/ {
# 許可するオリジン(特定のフロントエンドのドメインを指定するのが安全です)
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
# 許可するHTTPメソッド
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
# 許可するリクエストヘッダー(認証トークンなどを通すために重要です)
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
# プリフライトリクエスト(OPTIONS)に対する即座の応答
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
add_header 'Access-Control-Max-Age' 1728000; # プリフライト結果をキャッシュする秒数(約20日)
return 204; # 内容のない成功レスポンスを返す
}
}
このように、サーバー側で OPTIONS メソッドが飛んできたときに「OKですよ」と返せるようにしておくことが、CORSエラーを防ぐための決定打になります。
—
セキュリティリスクの制御と現場の注意点
「なんだ、じゃあ Access-Control-Allow-Origin: * にしておけば、CORSエラーなんて一生出ないしラクチンじゃん!」と思ったそこのあなた。ちょっと待ってくださいね。ここがインフラ・セキュリティの腕の見せ所です。
すべてを許可するアスタリスク(*)は、開発環境や誰でも自由に使える公開API(気象情報など)であれば問題ありません。しかし、ユーザーの個人情報や機密データを扱う業務システムやSaaSのAPIでこれをやってしまうと、悪意ある外部サイトからユーザーのセッションを踏み台にされて、データを勝手に盗み出されるリスク(CSRFや情報漏洩)が一気に跳ね上がります。
実務でAPIを設計・運用する際は、以下の鉄則を守りましょう。
1. 許可するオリジンは必要最小限に絞る
* で妥協せず、自社のフロントエンドが稼働する正確なドメイン(例: https://app.example.com)を明示的に指定します。
2. 認証情報(CookieやAuthorizationヘッダー)を伴う通信の扱い
Access-Control-Allow-Credentials: true を有効にする場合、Access-Control-Allow-Origin に * を指定することはブラウザの仕様で禁止されています。必ず特定のオリジンを記述してください。
—
まとめ
今回は、CORSのプリフライトリクエストについて、郵便配達の例えを交えながら一歩ずつ解説しました。
- CORS は、異なるドメイン間での安全な通信を守るためのブラウザの仕組み。
- プリフライトリクエスト(
OPTIONS) は、本番通信の前にブラウザが行う「安全確認のお伺い」。 - サーバー側で適切に
Access-Control-Allow-Originなどのヘッダーを設定し、セキュリティリスクをコントロールしながら快適なデータ連携を実現する。
最初は難しく見えるHTTPヘッダーのやり取りも、「ブラウザが私たちの安全を守るために、一生懸命確認してくれているんだな」という背景を知るだけで、ぐっと親しみやすく感じられるはずです。
現場でCORSエラーに遭遇したときは、焦らずブラウザの開発者ツールの「ネットワークタブ」を開き、「OPTIONS リクエストが飛んでいるか」「サーバーが正しい許可証(ヘッダー)を返しているか」を優しく観察してみてくださいね。
それでは、また次回の深淵でお会いしましょう!
コメント