目次
- 同一オリジンポリシーとは何か
- オリジンの定義
- 同一オリジンポリシーの役割
- 制限されるものとされないもの
- CORSの基本的な仕組み
- シンプルリクエスト
- プリフライトリクエスト
- 資格情報を含むリクエスト
- Express.jsでのCORS設定
- corsパッケージを使用する方法(推奨)
- 手動で設定する方法
- Nginx・Caddyでのリバースプロキシ設定
- Nginxでの設定
- Caddyでの設定
- よくあるCORSエラーと解決方法
- エラー1:Access-Control-Allow-Originヘッダーがない
- エラー2:ワイルドカードとcredentialsの併用
- エラー3:プリフライトリクエストの失敗
- エラー4:Varyヘッダーの不足によるキャッシュ問題
- CORS設定のセキュリティベストプラクティス
- まとめ:CORSを正しく理解して設定しよう
「Access to fetch at '...' from origin '...' has been blocked by CORS policy」——フロントエンドとAPIを別々に開発していると、一度は遭遇するエラーメッセージです。
CORSエラーに対して、なんとなくヘッダーを追加して回避している方も多いのではないでしょうか。本記事では、CORSの仕組みを基礎から理解し、適切な設定を行えるようになることを目指します。
同一オリジンポリシーとは何か
CORSを理解するには、まず「同一オリジンポリシー(Same-Origin Policy)」を知る必要があります。これはWebセキュリティの根幹をなす仕組みです。
オリジンの定義
オリジンは「スキーム(プロトコル)」「ホスト(ドメイン)」「ポート番号」の3つの組み合わせで決まります。
// オリジンの構成要素
https://www.example.com:443/path/page
│ │ │
スキーム ホスト ポート
// 同一オリジンの判定例
https://example.com/page1 と https://example.com/page2 → 同一オリジン ✅
https://example.com と http://example.com → 異なるオリジン ❌(スキームが違う)
https://example.com と https://api.example.com → 異なるオリジン ❌(ホストが違う)
https://example.com と https://example.com:8080 → 異なるオリジン ❌(ポートが違う)
同一オリジンポリシーの役割
同一オリジンポリシーは、あるオリジンのWebページが別のオリジンのリソースに無制限にアクセスすることを制限するブラウザのセキュリティ機構です。
この制限がないと、以下のような攻撃が容易に成立してしまいます。
攻撃シナリオの例
1. ユーザーが銀行サイト(bank.example.com)にログインしている
2. 攻撃者のサイト(evil.example.com)を訪問する
3. 攻撃者のJavaScriptがbank.example.comのAPIを呼び出す
4. ブラウザがCookieを自動送信し、ユーザーの口座情報が取得される
同一オリジンポリシーがあるため、手順3でブラウザがレスポンスの読み取りをブロックし、攻撃が成立しません。
制限されるものとされないもの
同一オリジンポリシーで制限されるもの
・fetch/XMLHttpRequestによるAPIリクエスト(レスポンスの読み取りがブロックされる)
・iframe内の別オリジンコンテンツへのDOM操作
・Canvas要素で別オリジンの画像データを読み取る操作
制限されないもの
・<img>タグによる画像の読み込み
・<link>タグによるCSSの読み込み
・<script>タグによるJavaScriptの読み込み
・<form>タグによるフォーム送信
CORSの基本的な仕組み
CORS(Cross-Origin Resource Sharing)は、同一オリジンポリシーの制限を安全に緩和するための仕組みです。サーバーが「このオリジンからのアクセスを許可する」とHTTPヘッダーで宣言することで、ブラウザがクロスオリジンリクエストを許可します。
シンプルリクエスト
以下の条件をすべて満たすリクエストは「シンプルリクエスト」として扱われ、プリフライトリクエストなしで直接送信されます。
・メソッドがGET、HEAD、POSTのいずれか
・ヘッダーがAccept、Accept-Language、Content-Language、Content-Type(一部の値のみ)に限定される
・Content-Typeがapplication/x-www-form-urlencoded、multipart/form-data、text/plainのいずれか
// シンプルリクエストの流れ
// 1. ブラウザがリクエストを送信(Originヘッダーを自動付与)
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
// 2. サーバーがCORSヘッダー付きでレスポンスを返す
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"data": "..."}
// 3. ブラウザがAccess-Control-Allow-Originを確認
// - Originと一致すればレスポンスを許可
// - 一致しなければCORSエラー
プリフライトリクエスト
シンプルリクエストの条件を満たさない場合、ブラウザはまず「プリフライトリクエスト」をOPTIONSメソッドで送信し、サーバーにリクエストが許可されるか確認します。
// プリフライトリクエストの流れ
// ステップ1: OPTIONSリクエスト(プリフライト)
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.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://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
// ステップ3: 本来のリクエスト(プリフライトが成功した場合のみ)
PUT /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
{"name": "山田太郎"}
// ステップ4: サーバーのレスポンス
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Max-Ageヘッダーにより、プリフライトの結果をブラウザがキャッシュします。指定した秒数の間、同じリクエストのプリフライトは省略されます。
資格情報を含むリクエスト
Cookieや認証ヘッダーなどの資格情報をクロスオリジンリクエストに含めるには、追加の設定が必要です。
// クライアント側:credentials: 'include' を指定
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // Cookieを送信
});
// サーバー側:必要なヘッダーを返す
Access-Control-Allow-Origin: https://app.example.com // ワイルドカード(*) は不可
Access-Control-Allow-Credentials: true
重要な制約:credentials: 'include'を使用する場合、Access-Control-Allow-Originにワイルドカード(*)は使用できません。具体的なオリジンを指定する必要があります。
Express.jsでのCORS設定
Node.js(Express)でのCORS設定方法を解説します。
corsパッケージを使用する方法(推奨)
const express = require('express');
const cors = require('cors');
const app = express();
// 基本的な設定
app.use(cors({
origin: 'https://app.example.com', // 許可するオリジン
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400, // プリフライトキャッシュ:24時間
}));
// 複数オリジンの許可
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
'http://localhost:3000', // 開発環境
];
app.use(cors({
origin: (origin, callback) => {
// originがundefinedの場合はサーバー間通信(許可する)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORSポリシーによりブロックされました'));
}
},
credentials: true,
}));
// 特定のルートのみCORSを許可
app.get('/api/public', cors({ origin: '*' }), (req, res) => {
res.json({ message: '公開API' });
});
手動で設定する方法
// ミドルウェアで手動設定
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin'); // キャッシュの問題を防ぐ
}
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
return res.status(204).end();
}
next();
});
Nginx・Caddyでのリバースプロキシ設定
アプリケーションサーバーの前段にリバースプロキシを配置している場合、そこでCORSヘッダーを設定することもできます。
Nginxでの設定
# nginx.conf
server {
listen 443 ssl;
server_name api.example.com;
location /api/ {
# プリフライトリクエストの処理
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
return 204;
}
# 通常のリクエスト
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Caddyでの設定
# Caddyfile
api.example.com {
@cors_preflight method OPTIONS
handle @cors_preflight {
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
header Access-Control-Max-Age "86400"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Credentials "true"
header Vary "Origin"
reverse_proxy localhost:3000
}
よくあるCORSエラーと解決方法
開発中に遭遇しやすいCORSエラーとその解決方法をまとめます。
エラー1:Access-Control-Allow-Originヘッダーがない
// エラーメッセージ
// No 'Access-Control-Allow-Origin' header is present on the requested resource
// 原因:サーバーがCORSヘッダーを返していない
// 解決方法:サーバーにAccess-Control-Allow-Originヘッダーを設定する
エラー2:ワイルドカードとcredentialsの併用
// エラーメッセージ
// The value of the 'Access-Control-Allow-Origin' header must not be
// the wildcard '*' when the request's credentials mode is 'include'
// 原因:credentials: 'include'を使いながらOriginに*を指定している
// 解決方法:具体的なオリジンを指定する
// NG: Access-Control-Allow-Origin: *
// OK: Access-Control-Allow-Origin: https://app.example.com
エラー3:プリフライトリクエストの失敗
// エラーメッセージ
// Response to preflight request doesn't pass access control check
// 原因:OPTIONSリクエストに対する応答が適切でない
// 解決方法:
// 1. OPTIONSメソッドへの応答を実装する
// 2. 必要なAccess-Control-Allow-Methodsを返す
// 3. 必要なAccess-Control-Allow-Headersを返す
エラー4:Varyヘッダーの不足によるキャッシュ問題
// 原因:CDNやブラウザがCORSレスポンスをキャッシュし、
// 異なるオリジンに対して誤ったレスポンスを返してしまう
// 解決方法:Varyヘッダーを必ず設定する
res.setHeader('Vary', 'Origin');
// これにより、Originごとに異なるキャッシュが保持される
CORS設定のセキュリティベストプラクティス
CORSの設定を誤ると、同一オリジンポリシーの保護を無効化してしまう危険があります。以下のベストプラクティスを守りましょう。
1. ワイルドカード(*)の使用を最小限にする
公開APIなど、誰からのアクセスも許可すべき場合のみ使用してください。認証が必要なエンドポイントでは絶対にワイルドカードを使用しないでください。
2. Originの検証にはホワイトリストを使う
正規表現でのマッチングは避けてください。/example\.com$/のようなパターンは、evil-example.comにもマッチしてしまいます。
// 悪い例:正規表現でのマッチング
if (/example\.com$/.test(origin)) { // evil-example.comも通過してしまう
res.setHeader('Access-Control-Allow-Origin', origin);
}
// 良い例:完全一致のホワイトリスト
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
3. 不要なメソッド・ヘッダーを許可しない
必要最小限のメソッドとヘッダーのみを許可してください。
4. Access-Control-Max-Ageを適切に設定する
プリフライトリクエストの頻度を減らしつつ、設定変更が反映されるまでの時間も考慮してください。一般的に24時間(86400秒)が推奨値です。
5. エラーレスポンスにもCORSヘッダーを付与する
alwaysパラメータ(Nginx)やミドルウェアの配置順(Express)に注意し、エラーレスポンスでもCORSヘッダーが返されるようにしてください。
まとめ:CORSを正しく理解して設定しよう
CORSは、Webのセキュリティモデルの重要な構成要素です。エラーが出たから適当にヘッダーを追加するのではなく、仕組みを理解した上で適切に設定することが大切です。
本記事のポイントを振り返ります。
基本の理解
・同一オリジンポリシーはブラウザのセキュリティ機構
・CORSはその制限を安全に緩和する仕組み
・オリジンはスキーム・ホスト・ポートの組み合わせで決まる
リクエストの種類
・シンプルリクエスト:条件を満たせば直接送信される
・プリフライトリクエスト:OPTIONSメソッドで事前確認が行われる
・資格情報付きリクエスト:ワイルドカードが使用できない
セキュリティ対策
・ワイルドカードの使用は最小限にする
・Originの検証は完全一致のホワイトリストで行う
・Varyヘッダーでキャッシュの問題を防ぐ
CORSエラーに遭遇したら、まずブラウザの開発者ツールでリクエスト・レスポンスヘッダーを確認しましょう。エラーメッセージの内容を正しく読み取れば、ほとんどの問題は解決できます。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します