1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解でわかるWeb技術⑨】CORS ― クロスオリジンの壁を理解する

1
Posted at

はじめに

「fetch() でAPIを叩いたら、コンソールに赤字のエラーが出て何も返ってこない」

フロントエンド開発で 一度は必ずぶつかる壁、それが CORS(Cross-Origin Resource Sharing) です。

この記事では、CORSの根本にある 同一オリジンポリシー から、プリフライトリクエスト の仕組み、CORSヘッダー の設定方法まで、図解で解説します。

この記事は 「図解でわかるWeb技術の仕組み」シリーズ の第9回です。
第8回:キャッシュ戦略(ブラウザ・サーバー・CDN)を先に読むと、よりスムーズに理解できます。


1. 同一オリジンポリシーとは

「オリジン」の定義

CORSを理解するには、まず オリジン(Origin) の定義を正確に知る必要があります。

09_cors_same_origin.png

オリジンは、URLの スキーム(プロトコル)+ ホスト + ポート の3つの組み合わせで決まります。

https://example.com:443/page
│       │              │
スキーム  ホスト        ポート

3つが完全に一致する場合のみ「同一オリジン」 です。パスやクエリパラメータは関係ありません。

同一オリジンポリシーの役割

同一オリジンポリシー(Same-Origin Policy) とは、ブラウザが持つセキュリティ機構です。

あるオリジンから読み込まれたドキュメントやスクリプトが、別のオリジンのリソースにアクセスすることを制限する

なぜこの制限が必要なのか? もし制限がなければ、以下のような攻撃が可能になります。

1. ユーザーが悪意あるサイト(https://evil.com)にアクセス
2. evil.com のJSが fetch("https://bank.example.com/api/account") を実行
3. ユーザーのCookieが自動送信され、口座情報が evil.com に漏洩

同一オリジンポリシーがあるからこそ、ステップ3でブラウザがレスポンスをブロック してくれます。

重要な誤解ポイント
同一オリジンポリシーは リクエストの送信を止めるわけではありません。リクエストはサーバーに届き、レスポンスも返ってきます。ブラウザがJavaScriptからレスポンスを読み取ることをブロック するのです。


2. CORSエラーの発生メカニズム

では、実際にCORSエラーがどのように発生するのかを見てみましょう。

09_cors_blocked.png

よくあるCORSエラーメッセージ

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

このエラーが出た場合、問題はフロントエンドではなく、APIサーバー側 にあります。サーバーがCORSヘッダーを返していないことが原因です。

CORSが必要になる典型的なケース

ケース フロントエンド APIサーバー
SPA + API構成 https://app.com https://api.app.com
ローカル開発 http://localhost:3000 http://localhost:8080
外部API利用 https://mysite.com https://api.openai.com

いずれもオリジンが異なるため、CORSの設定が必要です。


3. シンプルリクエスト ― CORSヘッダーで許可する

CORSには 「シンプルリクエスト」 と 「プリフライトが必要なリクエスト」 の2種類があります。

シンプルリクエストの条件

以下の条件を すべて 満たすリクエストは「シンプルリクエスト」として扱われます。

  • HTTPメソッドが GET、HEAD、POST のいずれか
  • Content-Typeが以下のいずれか
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • カスタムヘッダーを含まない(Authorization ヘッダー等がない)

シンプルリクエストの流れ

09_cors_simple_flow.png

シンプルリクエストの場合、ブラウザはそのままリクエストを送信します。サーバーが Access-Control-Allow-Origin ヘッダーをレスポンスに含めていれば、ブラウザはJavaScriptにデータを渡します。

サーバー側の設定例(Express.js)

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

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://app.com');
  next();
});

app.get('/data', (req, res) => {
  res.json({ message: 'Hello from API' });
});

4. プリフライトリクエスト ― 本番前の「事前確認」

シンプルリクエストの条件を満たさない場合、ブラウザは プリフライトリクエスト(Preflight Request) を自動で送信します。

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

  • HTTPメソッドが PUT、DELETE、PATCH のいずれか
  • Content-Type: application/json を使う
  • Authorization ヘッダーを付ける
  • その他のカスタムヘッダーを付ける

実務では JSONでやり取りするAPIはほぼ全てプリフライトが発生 します。

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

09_cors_preflight.png

プリフライトは2段階で動きます。

Phase 1: OPTIONS リクエスト(プリフライト)

OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://app.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

ブラウザが「PUT メソッドと Authorization ヘッダーを使っていい?」とサーバーに確認します。

Phase 2: サーバーの応答

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

サーバーが「OK、許可する」と返すと、ブラウザは本番リクエストを送信します。

サーバー側の設定例(Express.js)

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://app.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Max-Age', '86400');

  // プリフライトリクエストにはすぐ返す
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

Access-Control-Max-Age でパフォーマンス改善
プリフライトの結果をキャッシュすることで、毎回OPTIONSリクエストが飛ぶのを防げます。86400(24時間)が一般的です。


5. CORSヘッダー一覧

CORSに関わるHTTPヘッダーを一覧で整理します。

09_cors_headers.png

特に重要な3つ

ヘッダー 役割 設定のポイント
Access-Control-Allow-Origin 許可するオリジン * は手軽だがCredentials併用不可
Access-Control-Allow-Methods 許可するHTTPメソッド プリフライトで使用
Access-Control-Allow-Headers 許可するリクエストヘッダー Authorization を忘れがち

Cookie を送信する場合(Credentials)

フロントエンドとAPIでCookieを共有する場合は、追加設定が必要です。

フロントエンド側

fetch('https://api.example.com/data', {
  credentials: 'include'  // Cookieを送信
});

サーバー側

Access-Control-Allow-Origin: https://app.com   # * は使えない
Access-Control-Allow-Credentials: true

Access-Control-Allow-Origin: * と credentials: 'include' は併用できない
Cookieを送信する場合は、必ず具体的なオリジンを指定してください。ワイルドカードを使うとブラウザがエラーを出します。


6. 実務でよくあるCORSの解決パターン

パターン1: cors ライブラリを使う(Node.js)

const cors = require('cors');

app.use(cors({
  origin: 'https://app.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

パターン2: Nginx でCORSヘッダーを付ける

location /api/ {
    add_header Access-Control-Allow-Origin "https://app.com";
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";

    if ($request_method = OPTIONS) {
        return 204;
    }

    proxy_pass http://backend;
}

パターン3: 開発環境ではProxyを使う(Vite / Next.js)

開発中は フロントエンドの開発サーバーにProxy設定 を入れるのが最も簡単です。

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
};

この方法なら、ブラウザから見ると同一オリジンになるため、CORSの問題が発生しません。


7. まとめ

項目 ポイント
オリジン スキーム + ホスト + ポート の3つ。1つでも違えばクロスオリジン
同一オリジンポリシー ブラウザのセキュリティ機構。異なるオリジンへのJSからのアクセスを制限
CORSエラー サーバーが Access-Control-Allow-Origin を返していないことが原因
シンプルリクエスト GET/HEAD/POST + 特定のContent-Type + カスタムヘッダーなし
プリフライト 条件を満たさない場合、ブラウザがOPTIONSリクエストで事前確認
Credentials Cookie送信時は Access-Control-Allow-Origin: * が使えない
開発環境 Proxy設定で回避するのが最も手軽

次回予告

次回の第10回では 「REST API 設計の基本原則」 を図解で解説します。リソース設計、エンドポイントの命名規則、ステータスコードの使い分け、そして「べからず集」まで。シリーズ最終回 です。


シリーズ目次

# テーマ
1 HTTPリクエスト/レスポンスの仕組み
2 Cookie・Session・JWTの違い
3 OAuth 2.0 の仕組み
4 DNS解決の流れ
5 HTTPS・TLS通信の仕組み
6 CDN の仕組みと役割
7 WebSocket ― 双方向リアルタイム通信
8 キャッシュ戦略(ブラウザ・サーバー・CDN)
9 CORS ― クロスオリジンの壁を理解する(この記事)
10 REST API 設計の基本原則

「この記事が役に立った」と思ったら、LGTM とストックをお願いします。

@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?