こんにちは!インフラアーキテクトの私です。日々、ネットワークの海を駆け巡るパケットたちと対話していますが、Webアプリの開発現場で最も頭を悩ませるトラブルの一つが、今回取り上げる「CORS(Cross-Origin Resource Sharing)」、そしてその裏側でこっそりやり取りされる「プリフライトリクエスト(OPTIONSメソッド)」です。
「フロントエンドからAPIを叩くだけなのに、なぜかブラウザにブロックされる……」
「コンソールに見たこともないエラーが出た……」
そんな経験はありませんか?一歩ずつ、身近な例えを交えながらその仕組みを紐解いていきましょう!
—
1. そもそも「CORS」と「プリフライト」って何?身近な例えで考えてみよう
ネットワークの世界に初めて触れる方にとって、CORSという言葉は少し硬く聞こえるかもしれませんね。でも、安心してください。基本の考え方は、私たちの身近にある「セキュリティチェック」とまったく同じなんです。
郵便配達で例えるセキュリティのルール
想像してみてください。あなたの家(オリジンA:例えば https://frontend.com)にいるとします。そこに、見知らぬ人から手紙が届きました。
もし、あなたのプライベートな手紙や財産を、見知らぬ他人が勝手に持ち出したり、覗き見できたりしたら大変ですよね?
Webの世界でも同じです。ブラウザはあなたの大切な個人情報を守るために、「自分がいる場所(Webサイト)と違う場所(APIサーバー)へ、勝手に荷物を送ったり受け取ったりしてはいけない」という、とても厳しいセキュリティルール(同一同一オリジンポリシー)を持っています。
しかし、現代のWebアプリでは、フロントエンドの画面と、データを処理するAPIサーバーが別々の場所(異なるドメインやポート)にあることはごく当たり前です。
「この別ドメインのサーバーとは、お互いに信頼し合っているから通信しても大丈夫だよ!」とブラウザに教えてあげる仕組みが、CORS(Cross-Origin Resource Sharing)なのです。
いきなり本番の荷物を送らない!「下見」としてのOPTIONSリクエスト
さて、ここで本題のプリフライトリクエストが登場します。
お友達の家に大きな荷物を送る時、いきなり玄関のドアをドンドン叩いて大きな荷物を押し付けたら、「ちょっと待って!誰ですか!」と驚かれてしまいますよね。
大人のマナーとして、まずはインターホンを押して、「こういうサイズの荷物を、こういう中身で送ってもいいですか?」と相手に事前に確認を取るはずです。
この「事前の確認(下見)」こそが、HTTPの OPTIONS メソッドを使ったプリフライトリクエスト(Preflight Request:直訳すると「飛行前点検」)の正体です。
ブラウザは、私たちがJavaScriptからAPIを呼び出した時、本番の通信(GETやPOSTなど)をいきなり行うのではなく、その前にこっそり OPTIONS というメソッドで「この通信、許可してくれますか?」とサーバーにお伺いを立てているのです。
—
2. 実際のネットワークでは何が起きているのか?パケットの動きを覗き見
それでは、ブラウザとAPIサーバーの間で、実際どのようなやり取りが行われているのか、一連の流れを覗いてみましょう。
[ブラウザ (frontend.com)] [APIサーバー (api.backend.com)]
| |
| --- 1. プリフライト送信 (OPTIONS) -------> | 「これからGET/POSTしていい?」
| <--- 2. 許可の返答 (Access-Control-...) --- | 「いいよ!許可するヘッダーはこれね」
| |
| --- 3. 本番のリクエスト (POST/GET) ------> | 「データをちょうだい!」
| <--- 4. 本番のレスポンス ----------------- | 「はい、データどうぞ!」
このように、1つのAPIを叩くだけで、実は裏側で「お伺い(OPTIONS)」と「本番」の2往復(場合によっては1往復)の通信が行われているのです。
サーバーから返される魔法のヘッダーたち
APIサーバーが「この通信は安全だから許可するよ!」と返事をする際、いくつかの重要な「通行手形(HTTPヘッダー)」を一緒に返してあげます。これが、トラブルシューティングでよく目にする以下のヘッダーたちです。
Access-Control-Allow-Origin: どのドメインからのアクセスを許可するか(例:https://frontend.comやすべてを許可する*)Access-Control-Allow-Methods: どのHTTPメソッドを許可するか(例:GET, POST, PUT, DELETEなど)Access-Control-Allow-Headers: リクエストにどんなカスタムヘッダーが含まれていても良いか(例:AuthorizationやContent-Typeなど)
もし、サーバーがこれらのヘッダーを正しく返してくれなかったり、許可されていないドメインからのアクセスだったりすると、ブラウザは「おっと、この通信は許可されていないな」と判断し、本番のリクエストを送る前にバッサリと通信をブロックします。これが、開発現場でよく泣かされるCORSエラーのメカニズムです。
—
3. 実務で役立つ!サーバー側でのCORS設定サンプル
インフラやバックエンドを構築する際、このプリフライトリクエスト(OPTIONS)に対して適切に応答を返す設定が必須になります。
ここでは、よく使われるWebフレームワークやサーバーの設定例を見ていきましょう。実務でそのままコピーして調整できるよう、日本語の丁寧なコメントを添えておきますね。
① Nginxをリバースプロキシとして使う場合の設定例
フロントエンドとバックエンドの間にNginxを挟む場合、OPTIONS メソッドが飛んできた時の専用の処理を書いてあげることがよくあります。
server {
listen 80;
server_name api.backend.com;
location /api/ {
# 1. プリフライトリクエスト(OPTIONS)を受け取った場合の特別処理
if ($request_method = 'OPTIONS') {
# 許可するオリジン(特定のフロントエンドドメインを指定するのがセキュアです)
add_header 'Access-Control-Allow-Origin' 'https://frontend.com' always;
# 許可するHTTPメソッド
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
# 許可するリクエストヘッダー(認証トークンやJSON形式を指定できるようにする)
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;
# プリフライトの結果をブラウザにキャッシュさせる時間(秒単位:ここでは10分間キャッシュ)
add_header 'Access-Control-Max-Age' 600 always;
# プレーンな成功ステータス(204 No Content)を返してここで通信を完了させる
return 204;
}
# 2. 通常の本番リクエスト(GETやPOSTなど)に対する設定
add_header 'Access-Control-Allow-Origin' 'https://frontend.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With' always;
# バックエンドのアプリケーションサーバーへ転送
proxy_pass http://localhost:3000;
}
}
② Python (FastAPI) を使う場合の設定例
近年のモダンなWeb開発で大人気のFastAPIでは、標準で用意されているミドルウェアを使うだけで、面倒な OPTIONS のハンドリングをすべて自動で行ってくれます。一歩ずつ、コードを読んでいきましょう。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# アプリケーションにCORSミドルウェアを登録する
app.add_middleware(
CORSMiddleware,
# 許可するオリジン(開発環境なら ["*"] でも良いが、本番環境では明示的に指定すべき)
allow_origins=["https://frontend.com"],
# Cookieや認証情報(Authorizationヘッダーなど)を含めた通信を許可するかどうか
allow_credentials=True,
# 許可するHTTPメソッド("*" はすべてのメソッドを許可)
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
# 許可するHTTPヘッダー("*" はすべてのヘッダーを許可)
allow_headers=["*"],
)
@app.get("/api/hello")
def read_hello():
return {"message": "CORSの壁を越えてデータが届きました!"}
このように、フレームワークやインフラレイヤーで「どの扉を開けておくか」をしっかりと定義してあげることで、ブラウザは安心して通信を通してくれるようになります。
—
4. トラブルシューティング:現場でCORSエラーに遭遇したら?
最後に、実務の現場で「あ、CORSエラーが出た!」となった時の、インフラエンジニア流の冷静なアプローチ手順を共有しておきますね。
1. ブラウザの開発者ツール(F12)の「Networkタブ」を開く
- 赤字で表示されているリクエストをクリックし、まずは
OPTIONSメソッドのリクエストが飛んでいるか確認します。
2. レスポンスヘッダーを確認する
- サーバーからの返答に
Access-Control-Allow-Originが正しく含まれているかチェックします。ヘッダー自体がごっそり抜けている場合は、APIサーバーやNginxの設定ミス、あるいはリバースプロキシでヘッダーが上書き・削除されてしまっている可能性大です。
3. URLやポート、スキーム(http / https)の不一致がないか見直す
- CORSは、ドメインだけでなく「ポート番号(例:
:3000と:8080)」や「プロトコル(httpとhttps)」が違うだけでも別のオリジンとみなされます。「ローカル開発環境でうっかり混ざっていなかったか」を優しく見直してあげましょう。
—
まとめ
いかがでしたでしょうか?
CORSのプリフライトリクエスト(OPTIONS)は、最初は難しく感じるかもしれませんが、要するに「ブラウザが私たちの安全を守るために、本番通信の前にこっそり行う『入場許可の確認』」にすぎません。
この仕組みをしっかりと理解しておけば、エラー画面に直面しても「お、ブラウザが下見の段階でストップをかけているな。サーバー側のヘッダー設定を見直そう」と、冷静かつスマートに対処できるようになります。
一歩ずつ、確実に知識を積み重ねて、快適なネットワーク・Web開発ライフを楽しみましょう!
コメント