こんにちは!Web開発をしていて「Access to fetch at '...' has been blocked by CORS policy」というエラーに遭遇したことはありませんか?私も最初はこのエラーに悩まされました。今回は、このCross-Origin(CORS)エラーについて、その仕組みから解決方法まで徹底的に解説します!
はじめに:CORSエラーとの初遭遇
多くの開発者が最初に遭遇するのは、こんなシナリオです:
// フロントエンド(localhost:3000)から
fetch('http://localhost:8080/api/users')
.then(response => response.json())
.then(data => console.log(data));
結果:
Access to fetch at 'http://localhost:8080/api/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
「え?ローカル環境なのになぜ?」と思った方、安心してください。これは正常な動作です!
そもそもCORS(Cross-Origin Resource Sharing)とは?
Same-Origin Policy(同一オリジンポリシー)
まず理解すべきは、ブラウザの基本的なセキュリティ機能である「同一オリジンポリシー」です。
オリジンの構成要素:
- プロトコル(http/https)
- ホスト(example.com)
- ポート番号(80, 443, 3000など)
// 同一オリジンの例
// 基準: https://example.com:443/page1
https://example.com/page2 // ✅ 同一オリジン
https://example.com:443/api/users // ✅ 同一オリジン(デフォルトポート)
// 異なるオリジンの例
http://example.com/api // ❌ プロトコルが違う
https://api.example.com/users // ❌ サブドメインが違う
https://example.com:8080/api // ❌ ポート番号が違う
CORSの役割
CORSは、この厳格な同一オリジンポリシーを安全に緩和するための仕組みです。サーバー側が「この外部オリジンからのアクセスを許可します」と明示的に宣言することで、クロスオリジンリクエストを可能にします。
CORSエラーが発生する仕組み
1. シンプルリクエストの場合
// シンプルリクエストの例
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'text/plain'
}
});
ブラウザの動作:
- リクエストを送信
- レスポンスを受信
-
Access-Control-Allow-Origin
ヘッダーをチェック - ヘッダーがない、または現在のオリジンが含まれていない場合、エラー
2. プリフライトリクエストの場合
// プリフライトが必要なリクエストの例
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: 'John' })
});
ブラウザの動作:
- OPTIONSメソッドでプリフライトリクエストを送信
- サーバーからのCORSヘッダーを確認
- 許可されていれば、実際のリクエストを送信
- 許可されていなければ、エラーで止まる
よくあるCORSエラーパターン集
パターン1: Access-Control-Allow-Originヘッダーなし
Access to fetch at 'http://api.example.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.
原因: サーバーがCORSヘッダーを設定していない
パターン2: オリジンの不一致
Access to fetch at 'http://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value
'http://localhost:8080' that is not equal to the supplied origin.
原因: サーバーで設定されたオリジンと実際のオリジンが異なる
パターン3: プリフライトリクエストの失敗
Access to fetch at 'http://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: Method POST is not allowed by Access-Control-Allow-Methods
in response to a preflight request.
原因: プリフライトでPOSTメソッドが許可されていない
パターン4: カスタムヘッダーの拒否
Access to fetch at 'http://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: Request header field authorization is not allowed by
Access-Control-Allow-Headers in response to a preflight request.
原因: カスタムヘッダー(Authorization等)が許可されていない
サーバー側での解決方法
1. Express.js での設定
基本的な設定
const express = require('express');
const app = express();
// 基本的なCORS設定
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// プリフライトリクエストへの対応
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
corsライブラリを使用
const cors = require('cors');
// 全てのオリジンを許可(開発環境のみ)
app.use(cors());
// 特定のオリジンのみ許可(本番環境推奨)
app.use(cors({
origin: ['http://localhost:3000', 'https://myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true // Cookieを含む場合
}));
2. Next.js API Routes での設定
// pages/api/users.js または app/api/users/route.js
export default function handler(req, res) {
// CORS ヘッダーを設定
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// 実際のAPI処理
res.status(200).json({ message: 'Hello World' });
}
3. Python Flask での設定
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# 基本的な設定
CORS(app)
# 詳細な設定
CORS(app,
origins=['http://localhost:3000', 'https://myapp.com'],
methods=['GET', 'POST', 'PUT', 'DELETE'],
allow_headers=['Content-Type', 'Authorization'])
@app.route('/api/users')
def get_users():
return {'users': []}
4. Spring Boot での設定
@RestController
@CrossOrigin(origins = "http://localhost:3000") // 特定オリジン
public class UserController {
@GetMapping("/api/users")
public List<User> getUsers() {
return userService.getAllUsers();
}
}
// グローバル設定
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
フロントエンド側での対処法
1. プロキシの使用(開発環境)
Vite での設定
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
Create React App での設定
/package.json
{
"name": "my-app",
"version": "0.1.0",
"proxy": "http://localhost:8080"
}
Next.js での設定
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:8080/api/:path*'
}
]
}
}
2. リクエストの調整
シンプルリクエストにする
// ❌ プリフライトが必要
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // カスタムヘッダー
},
body: JSON.stringify(data)
});
// ✅ シンプルリクエスト
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(data)
});
環境別の対策戦略
開発環境
- プロキシを使用(最も簡単)
- サーバー側でワイルドカード許可
- ブラウザのセキュリティを無効化(非推奨)
# Chrome でセキュリティを無効化(開発時のみ)
google-chrome --disable-web-security --user-data-dir="/tmp/chrome_dev_session"
ステージング/本番環境
- 特定のオリジンのみ許可
- 必要最小限のメソッド・ヘッダーのみ許可
- 環境変数でオリジンを管理
// 環境に応じたオリジン設定
const allowedOrigins = process.env.NODE_ENV === 'production'
? ['https://myapp.com', 'https://www.myapp.com']
: ['http://localhost:3000', 'http://localhost:3001'];
app.use(cors({
origin: allowedOrigins,
credentials: true
}));
よくある誤解と落とし穴
誤解1: 「CORSはサーバー側の問題」
真実: CORSはブラウザのセキュリティ機能です。PostmanやcURLでは発生しません。
# このコマンドは成功する(ブラウザを経由しないため)
curl -X GET http://api.example.com/users
誤解2: 「Access-Control-Allow-Origin: *で全て解決」
問題: 認証情報(Cookies、Authorization header)を含むリクエストでは*
が使えません。
// ❌ エラーになる
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', 'true');
// ✅ 正しい
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Credentials', 'true');
誤解3: 「HTTPSからHTTPへのリクエストはCORSで解決」
真実: Mixed Content Policy により、HTTPSからHTTPへのリクエストはCORSとは別の理由でブロックされます。
デバッグ方法とツール
1. ブラウザの開発者ツール
// Network タブでの確認ポイント
// 1. プリフライトリクエスト(OPTIONS)の有無
// 2. レスポンスヘッダーの確認
// 3. ステータスコード(200でもCORSエラーは発生する)
2. カスタムデバッグ関数
function debugCORS(url, options = {}) {
console.log('🚀 Sending request to:', url);
console.log('📦 Request options:', options);
return fetch(url, options)
.then(response => {
console.log('✅ Response status:', response.status);
console.log('📋 Response headers:');
for (let [key, value] of response.headers.entries()) {
console.log(` ${key}: ${value}`);
}
return response;
})
.catch(error => {
console.error('❌ CORS Error:', error.message);
throw error;
});
}
// 使用例
debugCORS('http://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));
3. プリフライトリクエストの確認
// プリフライトが送信されるかチェック
function willTriggerPreflight(method, headers) {
const simpleMethod = ['GET', 'HEAD', 'POST'].includes(method);
const simpleHeaders = Object.keys(headers).every(header =>
['accept', 'accept-language', 'content-language', 'content-type'].includes(header.toLowerCase())
);
const simpleContentType = !headers['content-type'] ||
['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'].includes(headers['content-type']);
return !(simpleMethod && simpleHeaders && simpleContentType);
}
console.log(willTriggerPreflight('POST', {'Content-Type': 'application/json'})); // true
セキュリティのベストプラクティス
1. 最小権限の原則
// ❌ 過度に緩い設定
app.use(cors({
origin: '*',
methods: '*',
allowedHeaders: '*'
}));
// ✅ 必要最小限の設定
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // プリフライトのキャッシュ時間
}));
2. 環境別の設定管理
// config/cors.js
const corsConfig = {
development: {
origin: ['http://localhost:3000', 'http://localhost:3001'],
credentials: true
},
production: {
origin: process.env.ALLOWED_ORIGINS.split(','),
credentials: true,
optionsSuccessStatus: 200
}
};
module.exports = corsConfig[process.env.NODE_ENV] || corsConfig.development;
3. 動的オリジン検証
app.use(cors({
origin: (origin, callback) => {
// オリジンなし(same-origin)またはモバイルアプリは許可
if (!origin) return callback(null, true);
// 許可リストとの照合
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
// 開発環境でのローカルホスト許可
if (process.env.NODE_ENV === 'development' && origin.includes('localhost')) {
return callback(null, true);
}
callback(new Error('Not allowed by CORS'));
}
}));
トラブルシューティングチェックリスト
サーバー側チェック項目
-
Access-Control-Allow-Origin
ヘッダーが設定されているか - オリジンの値が正確か(末尾のスラッシュに注意)
-
Access-Control-Allow-Methods
に必要なメソッドが含まれているか -
Access-Control-Allow-Headers
に必要なヘッダーが含まれているか - OPTIONSリクエストに正しく応答しているか
-
認証情報を使用する場合、
Access-Control-Allow-Credentials: true
が設定されているか
フロントエンド側チェック項目
- リクエストURL が正しいか
- プロトコル、ホスト、ポートが正確か
- 必要以上にカスタムヘッダーを送信していないか
- Content-Typeが適切か
- プロキシ設定が正しいか(開発環境)
ブラウザ・環境チェック項目
- ブラウザのキャッシュをクリアしたか
- 複数のブラウザで確認したか
- HTTPSとHTTPの混在がないか
- ネットワークタブでプリフライトリクエストを確認したか
まとめ
CORSエラーは最初は複雑に見えますが、仕組みを理解すれば適切に対処できます。重要なポイントは:
- CORSはブラウザのセキュリティ機能であり、サーバー側で適切に設定する必要がある
- 開発環境では緩く、本番環境では厳しく設定する
- 最小権限の原則に従って、必要最小限の権限のみ許可する
- 環境別の設定管理を適切に行う
この記事が皆さんのCORSエラー解決の助けになれば嬉しいです!何か質問やつまずいた点があれば、コメントで教えてください。