メインコンテンツにスキップ

🌐 CORSとは?

📖 定義

CORS(Cross-Origin Resource Sharing、オリジン間リソース共有)は、異なるオリジン(origin)のリソースにアクセスする権限をブラウザに許可するように伝えるセキュリティメカニズムです。Webブラウザは、セキュリティ上の理由から基本的に異なるオリジンのリソース要求を制限しますが、CORSはこれを安全に許可する方法です。

🎯 比喩で理解する

マンションのセキュリティシステム

想像してください。あなたはA棟のマンションに住んでいます:

  • 同一オリジン(Same-Origin): A棟101号からA棟管理室に電話 → 自由に通話可能 ✅
  • 異なるオリジン(Cross-Origin): A棟101号からB棟管理室に電話 → セキュリティ確認が必要 🔒
  • CORS設定: B棟が「A棟の住民も電話を受けます」と事前に許可 → 通話可能 ✅

CORSはこのように異なる建物(オリジン)のリソースにアクセスする際に必要な許可証です!

⚙️ 動作原理

1. オリジン(Origin)とは?

オリジンはプロトコル、ドメイン、ポートの組み合わせです:

https://www.example.com:443/page
│ │ │ │ │
│ │ │ │ └─ パス (オリジンとは無関係)
│ │ │ └────── ポート (省略時443)
│ │ └────────────────── ドメイン
│ └────────────────────── プロトコル
└───────────────────────────── オリジン (Origin)

2. 同一オリジン vs 異なるオリジン

// 現在のページ: https://www.example.com

同一オリジン (Same-Origin)
- https://www.example.com/page
- https://www.example.com/api/users

異なるオリジン (Cross-Origin)
- http://www.example.com // プロトコルが異なる
- https://api.example.com // ドメインが異なる
- https://www.example.com:8080 // ポートが異なる

3. CORSリクエストプロセス

Simple Request (単純リクエスト)

1. ブラウザ → サーバー: リクエスト送信
GET https://api.example.com/data
Origin: https://www.mysite.com

2. サーバー → ブラウザ: レスポンス
Access-Control-Allow-Origin: https://www.mysite.com

3. ブラウザ: レスポンス確認
- 許可されたオリジンなら → データ配信 ✅
- 許可されていなければ → CORSエラー ❌

Preflight Request (事前リクエスト)

1. ブラウザ → サーバー: 事前確認 (OPTIONS)
OPTIONS https://api.example.com/data
Origin: https://www.mysite.com
Access-Control-Request-Method: POST

2. サーバー → ブラウザ: 許可レスポンス
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Max-Age: 3600

3. ブラウザ → サーバー: 実際のリクエスト (POST)
POST https://api.example.com/data

4. サーバー → ブラウザ: データレスポンス

💡 実例

CORSエラー発生

// フロントエンド (https://www.mysite.com)
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

// ❌ コンソールエラー
// Access to fetch at 'https://api.example.com/users' from origin
// 'https://www.mysite.com' has been blocked by CORS policy:
// No 'Access-Control-Allow-Origin' header is present on the
// requested resource.

サーバーでCORSを許可する

Express.js (Node.js)

const express = require('express');
const cors = require('cors');
const app = express();

// 方法1: すべてのオリジンを許可 (開発環境)
app.use(cors());

// 方法2: 特定のオリジンのみ許可 (運用環境推奨)
app.use(cors({
origin: 'https://www.mysite.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // Cookieを含む
maxAge: 3600 // Preflight キャッシュ時間 (秒)
}));

// 方法3: 複数のオリジンを許可
const allowedOrigins = [
'https://www.mysite.com',
'https://admin.mysite.com'
];

app.use(cors({
origin: function(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));

// APIエンドポイント
app.get('/api/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});

app.listen(3000);

手動でヘッダーを設定

app.use((req, res, next) => {
// オリジン許可
res.header('Access-Control-Allow-Origin', 'https://www.mysite.com');

// メソッド許可
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');

// ヘッダー許可
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

// 認証情報許可
res.header('Access-Control-Allow-Credentials', 'true');

// Preflightリクエスト処理
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
});

Python Flask

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# すべてのオリジンを許可
CORS(app)

# 特定のオリジンのみ許可
CORS(app, resources={
r"/api/*": {
"origins": ["https://www.mysite.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type"]
}
})

@app.route('/api/users')
def get_users():
return {'users': ['Alice', 'Bob']}

Nginx設定

server {
listen 80;
server_name api.example.com;

location /api {
# CORSヘッダー追加
add_header 'Access-Control-Allow-Origin' 'https://www.mysite.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;

# Preflightリクエスト処理
if ($request_method = 'OPTIONS') {
return 204;
}

proxy_pass http://backend;
}
}

🤔 よくある質問

Q1. CORSはなぜ必要なのですか?

A: セキュリティのためです。CORSがなければ:

// 悪意のあるサイト (evil.com)
fetch('https://bank.com/api/transfer', {
method: 'POST',
credentials: 'include', // ユーザーの銀行Cookieを含む
body: JSON.stringify({
to: 'hacker-account',
amount: 1000000
})
});

// CORSのおかげでこのような攻撃がブロックされます!

Q2. CORSエラーの解決方法は?

A: 状況別の解決方法:

// 1. サーバー制御可能 → サーバーでCORSヘッダーを追加 (推奨)
app.use(cors({ origin: 'https://frontend.com' }));

// 2. サーバー制御不可能 → プロキシサーバーを使用
// package.json (Create React App)
{
"proxy": "https://api.example.com"
}

// 3. 開発環境 → プロキシミドルウェア
// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
app.use('/api', createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true
}));
};

// 4. ブラウザ拡張機能 (開発専用!)
// "CORS Unblock" などの拡張機能を使用
// ⚠️ 運用環境では絶対に使用しないでください!

Q3. credentials: 'include'はいつ使用しますか?

A: CookieやAuthentication情報を一緒に送信する時に使用します:

// フロントエンド
fetch('https://api.example.com/profile', {
credentials: 'include' // Cookieを含む
});

// バックエンド - 必ず明示的にオリジンを指定
app.use(cors({
origin: 'https://www.mysite.com', // '*' は使用不可!
credentials: true
}));

// ❌ 間違った例
app.use(cors({
origin: '*', // ワイルドカード
credentials: true // credentialsと一緒に使用不可!
}));

Q4. Simple Request vs Preflight Request の違いは?

A: 条件によって異なります:

// ✅ Simple Request (すぐにリクエスト)
// - メソッド: GET, HEAD, POST
// - ヘッダー: Accept, Content-Type などの基本ヘッダーのみ
// - Content-Type: text/plain, multipart/form-data,
// application/x-www-form-urlencoded

fetch('https://api.example.com/data', {
method: 'GET'
});

// ⚠️ Preflight Request (事前確認後にリクエスト)
// - メソッド: PUT, DELETE, PATCH
// - カスタムヘッダー: Authorization など
// - Content-Type: application/json

fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Preflight 必要
'Authorization': 'Bearer token' // Preflight 必要
},
body: JSON.stringify({ name: 'Alice' })
});

Q5. CORSとCSRFの違いは?

A: 全く異なるセキュリティ概念です:

CORS (Cross-Origin Resource Sharing)
├─ 目的: 異なるオリジンのリソースアクセスを許可
├─ 動作: ブラウザがレスポンスをブロック/許可
└─ 解決: サーバーで許可ヘッダーを追加

CSRF (Cross-Site Request Forgery)
├─ 目的: 偽造されたリクエストを防止
├─ 動作: 悪意のあるサイトからリクエストを送信
└─ 解決: CSRFトークンを使用

🎓 次のステップ

CORSを理解したら、次を学習してみましょう:

  1. HTTPSとは? (文書作成予定) - もう一つの重要なWebセキュリティ
  2. JWTトークン (文書作成予定) - API認証方法
  3. APIとは? - API基礎概念

デバッグツール

// ブラウザ開発者ツールで確認
// Network タブ → リクエスト選択 → Headers タブ

// Request Headers
Origin: https://www.mysite.com
Access-Control-Request-Method: POST

// Response Headers
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Max-Age: 3600

🎬 まとめ

CORSはWebセキュリティの核心概念です:

  • オリジン: プロトコル + ドメイン + ポートの組み合わせ
  • Same-Origin Policy: 基本的に異なるオリジンをブロック
  • CORS: 安全に異なるオリジンを許可
  • Preflight: 複雑なリクエスト前の事前確認

CORSエラーが発生しても慌てないで、サーバーで適切なヘッダーを設定して安全に解決しましょう! 🌐✨