フリーキーズ | 独学プログラミング

CORS設定とプリフライトリクエストの落とし穴!初心者が知るべきセキュリティの基礎

リンドくん

リンドくん

たなべ先生、APIを作ってフロントエンドから呼び出そうとしたら「CORSエラー」って出たんですけど...これって何ですか?

たなべ

たなべ

あぁ、Web開発では誰もが一度は遭遇する壁だね!
CORSはセキュリティのための仕組みなんだけど、正しく理解していないと開発の大きな障害になっちゃうんだ。今日はその仕組みと対処法をしっかり学んでいこう。

Web開発を始めたばかりの方が、必ずと言っていいほど遭遇するのが「CORSエラー」です。
フロントエンドからバックエンドのAPIを呼び出そうとしたとき、ブラウザのコンソールに赤いエラーメッセージが表示され、途方に暮れた経験はないでしょうか?

Access to fetch at 'http://api.____.com/data' from origin 
'http://localhost:3000' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

このエラーメッセージ、見たことありますよね。
実はこれ、ブラウザがあなたのWebアプリケーションを守ろうとしている証なんです。

本記事では、CORSとプリフライトリクエストの基本から、よくある落とし穴とその解決法まで、初心者の方でも理解できるよう詳しく解説していきます。
この知識は、モダンなWeb開発では必須のスキルです。ぜひ最後まで読んで、CORSを味方につけてください!

プログラミング学習でお悩みの方へ

HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。

✓ 質問し放題

✓ β版公開中(2025年内の特別割引)

HackATAの詳細を見る

CORSとは何か?なぜ必要なのか

リンドくん

リンドくん

そもそもCORSって何の略なんですか?なんでこんな面倒な仕組みがあるんでしょう...

たなべ

たなべ

CORSはCross-Origin Resource Sharing(オリジン間リソース共有)の略なんだ。
実はこれ、Webをより安全にするためのセキュリティ機能なんだよ。歴史から見ていこうか。

Same-Origin Policy(同一オリジンポリシー)の存在

CORSを理解するには、まずSame-Origin Policy(同一オリジンポリシー)という概念を知る必要があります。
これはブラウザに組み込まれた基本的なセキュリティ機能で、異なるオリジン(origin)からのリソースへのアクセスを制限するものです。

「オリジン」とは、以下の3つの要素の組み合わせで決まります。

  • プロトコル(http、https)
  • ホスト(ドメイン名)
  • ポート番号

例えば、以下のURLを見てみましょう。

https://____.com:443/api/users

この場合のオリジンは https://____.com:443 です。

なぜSame-Origin Policyが必要なのか

もしSame-Origin Policyがなかったらどうなるでしょうか?
悪意のあるWebサイトが、あなたのログイン情報を使って銀行のサイトにアクセスし、勝手に送金してしまう...そんなことが可能になってしまいます。

具体的なシナリオを見てみましょう。

  1. あなたが銀行のサイト(https://A.com)にログインしている
  2. 別のタブで悪意のあるサイト(https://B.com)を開いてしまう
  3. そのサイトのJavaScriptがhttps://A.comのAPIを呼び出そうとする
  4. Same-Origin Policyによってブロックされる

このように、Same-Origin Policyはユーザーを守るための重要な防御壁なのです。

CORSの役割 - 安全な例外を作る

しかし現代のWeb開発では、異なるオリジン間での通信が必要なケースが多々あります。

  • フロントエンドとバックエンドのドメインが異なる場合
  • 外部APIを利用する場合
  • CDNからリソースを読み込む場合

そこで登場するのがCORSです。
CORSはサーバー側が明示的に許可した場合のみ、異なるオリジンからのアクセスを許可する仕組みです。

つまり、Same-Origin Policyという厳格なルールに対して、サーバーが「このオリジンからのアクセスは信頼できるから許可します」と宣言できるのがCORSなのです。

CORSの基本的な仕組みと設定方法

リンドくん

リンドくん

じゃあ、具体的にどうやってCORSを設定すればいいんですか?

たなべ

たなべ

基本的にはHTTPレスポンスヘッダにいくつかの情報を追加するだけなんだ。
でも、その設定を間違えるとセキュリティの穴になっちゃうから注意が必要だよ。

基本的なCORSヘッダ

CORSを有効にするための最も基本的なヘッダは Access-Control-Allow-Origin です。

// Node.js + Expressの例
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://frontend.____.com');
  next();
});

このヘッダを設定することで、指定したオリジン(この例では https://frontend.____.com)からのリクエストを許可できます。

よく使われるCORSヘッダ一覧

CORS設定で使用される主要なヘッダは以下の通りです。

  • Access-Control-Allow-Origin - どのオリジンからのアクセスを許可するか
  • Access-Control-Allow-Methods - 許可するHTTPメソッド(GET、POST、PUTなど)
  • Access-Control-Allow-Headers - 許可するリクエストヘッダ
  • Access-Control-Allow-Credentials - クッキーを含むリクエストを許可するか
  • Access-Control-Max-Age - プリフライトリクエストの結果をキャッシュする時間

実践的な設定例

以下は、よくある実用的なCORS設定の例です。

// Express.jsでのCORS設定例
const express = require('express');
const app = express();

app.use((req, res, next) => {
  // 特定のオリジンのみ許可
  res.setHeader('Access-Control-Allow-Origin', 'https://frontend.____.com');
  
  // 複数のHTTPメソッドを許可
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  
  // カスタムヘッダを許可
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  
  // クッキーを含むリクエストを許可
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  
  // プリフライトリクエストの結果を1時間キャッシュ
  res.setHeader('Access-Control-Max-Age', '3600');
  
  next();
});

危険な設定 - ワイルドカード使用時の注意

初心者がやりがちな危険な設定があります。

// ❌ 危険な設定例
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');

この設定は絶対にやってはいけません
ワイルドカード(*)ですべてのオリジンを許可しつつ、クレデンシャル(認証情報)も許可しているため、どんなサイトからでもあなたのAPIにアクセスできてしまいます。

正しくは、以下のどちらかを選択すべきです。

// ✅ パターン1: 特定のオリジンのみ許可(クレデンシャルあり)
res.setHeader('Access-Control-Allow-Origin', 'https://A.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');

// ✅ パターン2: すべてのオリジンを許可(クレデンシャルなし)
res.setHeader('Access-Control-Allow-Origin', '*');
// Access-Control-Allow-Credentialsは設定しない

プリフライトリクエストの仕組みと落とし穴

リンドくん

リンドくん

先生、ネットワークタブを見たら、1回のリクエストのつもりが2回送られてるんですけど...バグですか?

たなべ

たなべ

それはバグじゃなくてプリフライトリクエストだよ!
ブラウザが本番のリクエストを送る前に「このリクエスト送っても大丈夫?」って事前確認してるんだ。

プリフライトリクエストとは

プリフライトリクエストは、特定の条件を満たすクロスオリジンリクエストの前に、ブラウザが自動的に送信するOPTIONSメソッドのリクエストです。

これは「本番のリクエストを送っても安全か」をサーバーに確認するための仕組みです。

プリフライトが発生する条件

すべてのクロスオリジンリクエストでプリフライトが発生するわけではありません。
以下のような「単純リクエスト(Simple Request)」以外の場合に発生します。

プリフライトが発生しない単純リクエストの条件

  • HTTPメソッドが GET、HEAD、POST のいずれか
  • カスタムヘッダを使用していない
  • Content-Typeが以下のいずれか
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

プリフライトが発生するケース

// ❌ プリフライトが発生する例
fetch('https://api.____.com/data', {
  method: 'PUT',  // PUTメソッド
  headers: {
    'Content-Type': 'application/json',  // JSONを送信
    'Authorization': 'Bearer token123'   // カスタムヘッダ
  },
  body: JSON.stringify({ name: 'John' })
});

このリクエストは以下の理由でプリフライトが発生します。

  • HTTPメソッドがPUT(単純リクエストの条件外)
  • Content-Typeがapplication/json
  • カスタムヘッダ(Authorization)を使用

プリフライトリクエストの流れ

実際のHTTP通信を見てみましょう。

1. プリフライトリクエスト(OPTIONSメソッド)

OPTIONS /api/users HTTP/1.1
Host: api.____.com
Origin: https://frontend.____.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

2. サーバーのプリフライトレスポンス

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.____.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

3. 本番のリクエスト(実際のPUTリクエスト)

プリフライトが成功して初めて、本番のPUTリクエストが送信されます。

よくある落とし穴とその解決法

落とし穴① OPTIONSメソッドのハンドリング忘れ

多くの初心者が陥る問題は、OPTIONSメソッドに対するレスポンスを返していないことです。

// ❌ 間違った実装例
app.put('/api/users', (req, res) => {
  // PUT用の処理のみ実装
  res.json({ success: true });
});

この実装では、プリフライトリクエスト(OPTIONSメソッド)が来たときに適切なレスポンスを返せません。

// ✅ 正しい実装例
app.options('/api/users', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://frontend.____.com');
  res.setHeader('Access-Control-Allow-Methods', 'PUT');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(204);
});

app.put('/api/users', (req, res) => {
  // 本番のPUTリクエストの処理
  res.json({ success: true });
});

落とし穴② プリフライトのキャッシュ設定忘れ

プリフライトリクエストは毎回送信されると、パフォーマンスに影響します。
Access-Control-Max-Ageヘッダで適切にキャッシュしましょう。

// プリフライトの結果を24時間キャッシュ
res.setHeader('Access-Control-Max-Age', '86400');

落とし穴③ 開発環境と本番環境での設定ミス

開発時はlocalhostでテストするため、以下のような設定をしがちです。

// ❌ 開発環境でのみ動くコード
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

本番環境にデプロイすると、オリジンが変わるため動かなくなります。
環境変数を使って設定を切り替えましょう。

// ✅ 環境に応じた設定
const allowedOrigin = process.env.NODE_ENV === 'production'
  ? 'https://frontend.____.com'
  : 'http://localhost:3000';

res.setHeader('Access-Control-Allow-Origin', allowedOrigin);

セキュリティチェックリスト

CORS設定を行う際は、以下の点を必ず確認してください。

  1. 本当に必要なオリジンのみ許可しているか
  2. ワイルドカード使用時にクレデンシャルを許可していないか
  3. 本番環境のオリジンが正しく設定されているか
  4. 不要なHTTPメソッドを許可していないか
  5. 機密情報を含むエンドポイントで適切な認証を行っているか

デバッグのコツ

CORSエラーをデバッグする際は、以下の手順で確認しましょう。

  1. ブラウザの開発者ツールを開く
  2. Networkタブでリクエスト/レスポンスヘッダを確認
  3. プリフライトリクエスト(OPTIONSメソッド)が送られているか確認
  4. レスポンスヘッダに必要なCORSヘッダが含まれているか確認

特に重要なのは、プリフライトリクエスト(OPTIONS)と本番リクエスト(GET/POSTなど)の両方のレスポンスヘッダを確認することです。

まとめ

リンドくん

リンドくん

CORSって最初は面倒だなと思ってましたけど、セキュリティのために大事な仕組みなんですね!

たなべ

たなべ

その通り!最初は戸惑うかもしれないけど、仕組みを理解すれば怖くないんだ。
むしろ、適切なCORS設定はユーザーを守るための大切なスキルだからね。

CORS(Cross-Origin Resource Sharing)とプリフライトリクエストは、Web開発において避けて通れない重要な概念です。
今回学んだ内容を整理しましょう。

重要ポイントをおさらいしましょう。

  • CORSはセキュリティ機能 - ユーザーを守るために存在します
  • Same-Origin Policyの例外を作る仕組み - サーバーが明示的に許可した場合のみアクセス可能
  • プリフライトリクエストは事前確認 - 安全性を確保するためのブラウザの自動チェック
  • 適切な設定が重要 - セキュリティと利便性のバランスを考慮する

CORSは最初は難しく感じるかもしれませんが、一度理解してしまえば、セキュアなWeb開発の強い味方になります。
エラーに遭遇したときは、落ち着いてエラーメッセージを読み、設定を見直してみて下さい。

この記事をシェア

関連するコンテンツ