MeWrite Docs

Access to fetch has been blocked by CORS policy

ブラウザのCORSポリシーによりクロスオリジンリクエストがブロックされるエラー

概要

Access to fetch at '...' from origin '...' has been blocked by CORS policy は、ブラウザのセキュリティ機能であるCORS(Cross-Origin Resource Sharing)により、異なるオリジンへのリクエストがブロックされた場合に発生するエラーです。

エラーメッセージ

Access to fetch at 'https://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.
Access to XMLHttpRequest at 'https://api.example.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check.

原因

  1. サーバーがCORSヘッダーを返さない: Access-Control-Allow-Origin ヘッダーがない
  2. プリフライトリクエストの失敗: OPTIONS リクエストが拒否される
  3. 許可されていないオリジン: サーバーが特定のオリジンのみ許可
  4. 許可されていないヘッダー: カスタムヘッダーが許可されていない
  5. 認証情報の問題: credentials を含むリクエストの設定ミス

解決策(サーバー側)

1. Node.js (Express) での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const cors = require('cors');
const app = express();

// すべてのオリジンを許可(開発用)
app.use(cors());

// 特定のオリジンのみ許可(本番用)
app.use(cors({
    origin: 'https://myapp.com',
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true
}));

// 複数のオリジンを許可
app.use(cors({
    origin: ['https://myapp.com', 'https://admin.myapp.com'],
    credentials: true
}));

2. Python (Flask) での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

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

# 特定のオリジンのみ許可
CORS(app, origins=['https://myapp.com'])

# 詳細設定
CORS(app, resources={
    r"/api/*": {
        "origins": ["https://myapp.com"],
        "methods": ["GET", "POST"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})

3. Python (Django) での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # できるだけ上に配置
    'django.middleware.common.CommonMiddleware',
    ...
]

# すべてのオリジンを許可(開発用)
CORS_ALLOW_ALL_ORIGINS = True

# 特定のオリジンのみ許可(本番用)
CORS_ALLOWED_ORIGINS = [
    "https://myapp.com",
    "https://admin.myapp.com",
]

CORS_ALLOW_CREDENTIALS = True

4. PHP での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// すべてのオリジンを許可(開発用)
header("Access-Control-Allow-Origin: *");

// 特定のオリジンのみ許可(本番用)
$allowed_origins = ['https://myapp.com', 'https://admin.myapp.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';

if (in_array($origin, $allowed_origins)) {
    header("Access-Control-Allow-Origin: $origin");
}

header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Credentials: true");

// プリフライトリクエストの処理
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

5. Nginx での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    location /api/ {
        # プリフライトリクエスト
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://myapp.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Max-Age' 86400;
            return 204;
        }

        add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;

        proxy_pass http://backend;
    }
}

6. Apache での設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://myapp.com"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

# プリフライトリクエストの処理
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

解決策(クライアント側)

1. プロキシを使用(開発環境)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// vite.config.js
export default {
    server: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// webpack devServer
module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true
            }
        }
    }
};

2. credentials の正しい設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// fetch
fetch('https://api.example.com/data', {
    method: 'GET',
    credentials: 'include',  // Cookie を送信
    headers: {
        'Content-Type': 'application/json'
    }
});

// axios
axios.get('https://api.example.com/data', {
    withCredentials: true
});

3. JSONP(レガシー対応)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// CORS 非対応のAPIへの GET リクエスト(非推奨)
function jsonp(url, callback) {
    const script = document.createElement('script');
    const callbackName = 'jsonp_' + Date.now();
    window[callbackName] = (data) => {
        callback(data);
        delete window[callbackName];
        document.body.removeChild(script);
    };
    script.src = `${url}?callback=${callbackName}`;
    document.body.appendChild(script);
}

デバッグのコツ

ネットワークタブで確認

  1. ブラウザの開発者ツールを開く
  2. Network タブを選択
  3. 失敗したリクエストを確認
  4. Response Headers で CORS ヘッダーを確認

curl でテスト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# プリフライトリクエストのテスト
curl -X OPTIONS https://api.example.com/data \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -v

# 実際のリクエストのテスト
curl https://api.example.com/data \
  -H "Origin: http://localhost:3000" \
  -v

JavaScript の他のエラー

最終更新: 2025-12-08