こんにちは!ネットワークの裏側や、Webブラウザとサーバーの裏でこっそりやり取りされているHTTPの通信を見つめるのが大好物な、インフラアーキテクトです。
Webアプリケーションを作っていると、一度は必ずぶつかる壁がありますよね。「あれ、なんだかフロントエンドからAPIを叩けないぞ?」「コンソールに赤字でエラーが出ている……」そう、それが今回の主役である CORS(Cross-Origin Resource Sharing) と、その裏で行われる プリフライトリクエスト です。
「CORSって名前は聞くけれど、なんだか難しそう……」
「OPTIONSメソッドって一体なにをしているの?」
そんな風に思っているインフラ初心者や初学者のあなたへ!今回は難しいパケットの解析はひとまず置いておいて、身近な「郵便配達」の仕組みに例えながら、一歩ずつ優しく紐解いていきましょう。
—
1. なぜCORSが必要なの?現実世界で例えてみよう
まずは、CORSという仕組みがなぜ存在するのか、おなじみの現実世界に置き換えて考えてみましょう。
想像してみてください。あなたは今、厳重なセキュリティで守られた「会社の本社ビル(フロントエンドのアプリが動いている場所)」の中にいます。そして、別の場所にある「倉庫(APIサーバー)」から荷物を取り寄せたいとします。
もし、世の中のセキュリティがガバガバだったらどうなるでしょうか?
悪意のある人間が、あなたが普段使っているカバンにこっそり泥棒用のポケットを縫い付け、勝手に倉庫から機密文書を持ち出させてしまうかもしれません。Webの世界でこれをやってのけるのが、悪名高い「クロスサイトリクエスト(CSRF)」などの攻撃です。
これを防ぐために、Webブラウザには「同一生成元ポリシー(Same-Origin Policy)」という、とっても厳格な門番が立っています。
基本的には、「自分がいる場所(オリジン)と違う場所にあるサーバーへは、勝手に通信しちゃダメ!」というルールなんですね。
しかし、現代のWeb開発では、フロントエンド(例: https://example.com)とAPIサーバー(例: https://api.example.com)のドメインが分かれていることは日常茶飯事です。
「お隣さん同士なんだから、ちゃんと確認を取って安全なら通信を許可してあげたい!」
そこで登場するのが、CORS(Cross-Origin Resource Sharing)という仕組みなんです。
—
2. プリフライトリクエスト(事前確認)の正体とは?
では、本題の「プリフライトリクエスト」に進みましょう。プリフライト(Pre-flight)とは、直訳すると「飛行前の点検」という意味です。飛行機が飛び立つ前に、パイロットが計器やエンジンのチェックを入念に行うアレですね。
Webの世界でも同じです。ブラウザは、ちょっと複雑なAPIリクエスト(例えば、JSON形式のデータを送ったり、独自のカスタムヘッダーをつけたりする場合)をいきなりサーバーへ投げつけません。
代わりに、以下のようなステップを踏みます。
1. 下見(OPTIONSリクエスト)の送信
ブラウザが、APIサーバーに対して「これからこういうデータや方法で通信してもいいですか?」と、本番の前にこっそり確認のメモ(OPTIONSメソッド)を投げます。これがプリフライトリクエストです。
2. サーバーの返答
APIサーバーは、「ああ、キミか! その条件なら通信を許可するよ」というお返事(レスポンスヘッダー)を返します。
3. 本番の通信
ここで初めて、ブラウザは安心して本番のデータ(GETやPOSTなど)をサーバーに送信します。
つまり、プリフライトリクエストとは、「いきなり危険な球を投げ込まないように、事前に安全確認を行うスマートな紳士協定」のようなものなんです。
—
3. サーバー側での具体的な設定方法を見てみよう
さて、このプリフライトリクエストを成功させるためには、APIサーバー側できちんと「お返事」のルールを設定してあげる必要があります。
実務でよく使われる Node.js (Express) のコードを例に、具体的な設定を見てみましょう。難しく考えず、コメントを追いながら読んでみてくださいね。
const express = require('express');
const app = express();
// CORSを制御するための設定(お返事に含めるハンコのようなもの)
app.use((req, res, next) => {
// 1. どのドメインからのアクセスを許可するか(* は「全て許可」ですが、本番環境では特定のドメインを指定するのが安全です)
res.setHeader('Access-Control-Allow-Origin', 'https://my-frontend-app.com');
// 2. どんなHTTPメソッド(通信の種類)でのアクセスを許可するか
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 3. どんなHTTPヘッダー(パスワードやトークンなど)を含めることを許可するか
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// もしリクエストが「事前の確認(OPTIONS)」だったら、ここで即座にOKを返す
if (req.method === 'OPTIONS') {
return res.sendStatus(200); // 「問題なーい、進んでよし!」と伝える
}
next(); // 次の処理(本番のAPI処理)へ進む
});
// 本番のAPIエンドポイント
app.post('/api/data', (req, res) => {
res.json({ message: 'CORSの壁を無事に突破してデータが届きました!' });
});
app.listen(3000, () => {
console.log('APIサーバーが起動しました!');
});
このコードの中にある Access-Control-Allow-Origin や Access-Control-Allow-Methods といったヘッダーが、ブラウザとサーバーを繋ぐ大切なパスポートの役割を果たしています。
—
4. トラブルシューティング:CORSエラーに出会ったら?
現場で開発をしていると、ブラウザのコンソールにこんな憎らしいエラーが表示されることがあります。
> *Access to fetch at ‘https://api.example.com/data’ from origin ‘https://my-frontend-app.com’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The ‘Access-Control-Allow-Origin’ header is present on the resource.}*
もしこのエラーに出会ったら、焦らずに以下の3つをチェックしてみましょう。
1. OPTIONSリクエストに対して、サーバーが正しく200番台のステータスを返しているか?
たまに、フレームワークのルーティング設定ミスで OPTIONS メソッドを受け付けず、404 Not Found や 500 Internal Server Error を返してしまっているケースがあります。
2. 許可したいヘッダーが Access-Control-Allow-Headers に漏れていないか?
例えば、フロントエンドから X-Custom-Token という独自のヘッダーを送っているのに、サーバー側がそれを許可していなかった場合、プリフライトで弾かれてしまいます。
3. スペルミスやプロトコル(http / https)の不一致はないか?
http:// と https:// が混ざっているだけでも、ブラウザは別のオリジンとみなしてCORSエラーを発生させます。
—
まとめ
いかがでしたでしょうか?
一見すると難解な「CORSのプリフライトリクエスト」も、「ブラウザが安全のために行う事前の身元確認(お見合い)」だと捉えると、ぐっと親しみやすく感じられたのではないでしょうか。
インフラやネットワークの世界は、こうした「お互いの安全を守るためのルール」の積み重ねで成り立っています。仕組みが分かってしまえば、エラー画面も怖くありません!
ぜひ、今日の知識を実際の開発やトラブルシューティングに役立ててみてくださいね。それでは、快適なAPIライフを!
コメント