フロントエンドとバックエンドが分離した現代のWeb開発において、CORSエラーは避けて通れない課題です。開発者であれば一度は「Access to fetch has been blocked by CORS policy」というエラーメッセージに遭遇したことがあるでしょう。

この記事では、よくあるCORSエラーの原因とその解決方法、Express・Django・Spring Bootといった主要フレームワークでのCORS設定例、そして開発環境でのプロキシ設定について実践的に解説します。CORSエラーに遭遇した際に原因を特定し、迅速に解決できるようになることを目指します。

前提知識として、同一オリジンポリシーとCORSの基本的な仕組みについては同一オリジンポリシーとCORSの仕組みを完全理解するをご参照ください。

よくあるCORSエラーのパターンと解決方法

ブラウザのコンソールに表示されるCORSエラーメッセージは、その原因を特定するための重要な手がかりです。ここでは、代表的なエラーパターンとその解決方法を解説します。

パターン1: Access-Control-Allow-Originヘッダーの欠落

最も頻繁に遭遇するCORSエラーです。サーバーからのレスポンスにCORSを許可するヘッダーが含まれていない場合に発生します。

1
2
3
4
5
エラーメッセージの例

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

このエラーが発生する原因と解決方法は以下のとおりです。

原因 解決方法
サーバーにCORS設定がない サーバーサイドでCORSミドルウェアを追加する
CORSミドルウェアの設定ミス 許可するオリジンの設定を確認する
エラーレスポンス時にCORSヘッダーが付与されない エラーハンドラでもCORSヘッダーを返すよう設定する

サーバー側で適切な Access-Control-Allow-Origin ヘッダーを返すことで解決できます。

1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json

パターン2: プリフライトリクエストの失敗

カスタムヘッダーやPUT/DELETEメソッドなど、単純リクエストの条件を満たさない場合に発生するプリフライトリクエスト関連のエラーです。

1
2
3
4
5
6
エラーメッセージの例

Access to fetch at 'https://api.example.com/users' from origin 
'https://app.example.com' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

このエラーは、OPTIONSメソッドへの応答が正しく設定されていない場合に発生します。

原因 解決方法
OPTIONSメソッドのルートが未定義 OPTIONSリクエストを処理するルートを追加する
許可するメソッドが不足 Access-Control-Allow-Methods に必要なメソッドを追加する
許可するヘッダーが不足 Access-Control-Allow-Headers に必要なヘッダーを追加する

プリフライトリクエストへの正しいレスポンス例は以下のとおりです。

1
2
3
4
5
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

パターン3: 認証情報付きリクエストでのワイルドカード使用

Cookieや認証ヘッダーを含むリクエスト(Credentialed Request)で、オリジンにワイルドカードを使用している場合に発生するエラーです。

1
2
3
4
5
6
エラーメッセージの例

Access to fetch at 'https://api.example.com/users' from origin 
'https://app.example.com' has been blocked by CORS policy: 
The value of the 'Access-Control-Allow-Origin' header in the response 
must not be the wildcard '*' when the request's credentials mode is 'include'.

認証情報を含むリクエストでは、Access-Control-Allow-Origin にワイルドカード * を使用できません。明示的なオリジンを指定する必要があります。

1
2
3
4
5
6
7
// クライアント側のコード
fetch('https://api.example.com/users', {
  credentials: 'include',  // Cookieを含める
  headers: {
    'Content-Type': 'application/json'
  }
});
1
2
3
4
5
6
正しいサーバーレスポンス

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

パターン4: 許可されていないヘッダーの使用

カスタムヘッダーを使用しているが、サーバー側で許可されていない場合に発生するエラーです。

1
2
3
4
5
6
エラーメッセージの例

Access to fetch at 'https://api.example.com/users' from origin 
'https://app.example.com' has been blocked by CORS policy: 
Request header field x-custom-header is not allowed by 
Access-Control-Allow-Headers in preflight response.

解決するには、サーバー側で該当のヘッダーを許可リストに追加します。

1
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header

パターン5: 許可されていないメソッドの使用

PUT、DELETE、PATCHなどのメソッドがサーバー側で許可されていない場合に発生するエラーです。

1
2
3
4
5
6
エラーメッセージの例

Access to fetch at 'https://api.example.com/users/1' from origin 
'https://app.example.com' has been blocked by CORS policy: 
Method DELETE is not allowed by Access-Control-Allow-Methods 
in preflight response.

解決するには、サーバー側で該当のメソッドを許可リストに追加します。

1
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS

サーバーサイドでのCORS設定例

ここからは、主要なバックエンドフレームワークでのCORS設定方法を解説します。

ExpressでのCORS設定

Node.jsのExpressフレームワークでは、cors ミドルウェアを使用してCORSを設定できます。

実行環境と前提条件

  • Node.js 18.x 以上
  • Express 4.x または 5.x
  • npm または yarn がインストール済み

corsパッケージのインストール

1
npm install cors

基本的なCORS設定

すべてのオリジンからのリクエストを許可する最もシンプルな設定です。

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

const app = express();

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

app.get('/api/users', (req, res) => {
  res.json({ users: ['user1', 'user2'] });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

この設定により、すべてのリクエストに対して Access-Control-Allow-Origin: * ヘッダーが付与されます。

特定のオリジンのみ許可する設定

本番環境では、許可するオリジンを明示的に指定することが推奨されます。

 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();

const corsOptions = {
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  optionsSuccessStatus: 200  // レガシーブラウザ対応
};

app.use(cors(corsOptions));

app.get('/api/users', (req, res) => {
  res.json({ users: ['user1', 'user2'] });
});

app.listen(3000);

期待される動作として、https://app.example.com からのリクエストのみが許可され、他のオリジンからのリクエストはブロックされます。

複数オリジンを動的に許可する設定

開発環境と本番環境など、複数のオリジンを許可する場合は配列または関数で指定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const express = require('express');
const cors = require('cors');

const app = express();

// 許可するオリジンのリスト
const allowedOrigins = [
  'https://app.example.com',
  'https://staging.example.com',
  'http://localhost:3000'
];

const corsOptions = {
  origin: (origin, callback) => {
    // originがundefinedの場合はサーバー間通信
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
};

app.use(cors(corsOptions));

app.listen(3000);

特定のルートのみにCORSを適用

グローバルではなく、特定のエンドポイントのみにCORSを適用することもできます。

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

const app = express();

// 公開APIにはCORSを適用
app.get('/api/public', cors(), (req, res) => {
  res.json({ data: 'public data' });
});

// 内部APIにはCORSを適用しない
app.get('/api/internal', (req, res) => {
  res.json({ data: 'internal data' });
});

app.listen(3000);

DjangoでのCORS設定

Pythonの Djangoフレームワークでは、django-cors-headers パッケージを使用してCORSを設定できます。

実行環境と前提条件

  • Python 3.10 以上
  • Django 4.x または 5.x
  • pip がインストール済み

django-cors-headersのインストール

1
pip install django-cors-headers

基本設定(settings.py)

インストール後、settings.py で以下の設定を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# settings.py

INSTALLED_APPS = [
    # ... 既存のアプリ
    'corsheaders',
    # ... その他のアプリ
]

MIDDLEWARE = [
    # CORSミドルウェアは最上位に配置することを推奨
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ... その他のミドルウェア
]

重要な点として、CorsMiddlewareCommonMiddleware より前に配置する必要があります。これにより、CORSヘッダーが正しく処理されます。

すべてのオリジンを許可(開発環境向け)

1
2
3
# settings.py

CORS_ALLOW_ALL_ORIGINS = True

この設定は開発環境でのみ使用し、本番環境では使用しないでください。

特定のオリジンのみ許可(本番環境向け)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# settings.py

CORS_ALLOWED_ORIGINS = [
    'https://app.example.com',
    'https://staging.example.com',
]

# 正規表現でオリジンを指定することも可能
CORS_ALLOWED_ORIGIN_REGEXES = [
    r'^https://\w+\.example\.com$',
]

認証情報付きリクエストの許可

Cookieやセッションを使用する場合は、以下の設定を追加します。

1
2
3
4
5
6
7
# settings.py

CORS_ALLOWED_ORIGINS = [
    'https://app.example.com',
]

CORS_ALLOW_CREDENTIALS = True

許可するHTTPメソッドとヘッダーの設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# settings.py

CORS_ALLOW_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
]

CORS_ALLOW_HEADERS = [
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
]

プリフライトリクエストのキャッシュ設定

1
2
3
4
# settings.py

# プリフライトレスポンスを86400秒(24時間)キャッシュ
CORS_PREFLIGHT_MAX_AGE = 86400

Spring BootでのCORS設定

JavaのSpring Bootでは、複数の方法でCORSを設定できます。ここでは代表的な方法を紹介します。

実行環境と前提条件

  • Java 17 以上
  • Spring Boot 3.x
  • Maven または Gradle がインストール済み

アノテーションによるコントローラーレベルの設定

特定のコントローラーやメソッドにのみCORSを適用する場合は、@CrossOrigin アノテーションを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "https://app.example.com")
public class UserController {

    @GetMapping("/api/users")
    public List<User> getUsers() {
        return userService.findAll();
    }

    // メソッドレベルで異なる設定も可能
    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping("/api/public")
    public String getPublicData() {
        return "public data";
    }
}

グローバルCORS設定(WebMvcConfigurer)

アプリケーション全体にCORSを適用する場合は、WebMvcConfigurer を実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://app.example.com", "https://staging.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
            .allowedHeaders("Content-Type", "Authorization")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

この設定により、/api/** パターンに一致するすべてのエンドポイントにCORS設定が適用されます。

CorsFilterによる設定

より細かい制御が必要な場合は、CorsFilter を使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class CorsFilterConfig {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        
        // 許可するオリジン
        config.setAllowedOrigins(Arrays.asList(
            "https://app.example.com",
            "https://staging.example.com"
        ));
        
        // 許可するメソッド
        config.setAllowedMethods(Arrays.asList(
            "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"
        ));
        
        // 許可するヘッダー
        config.setAllowedHeaders(Arrays.asList(
            "Content-Type", "Authorization", "X-Requested-With"
        ));
        
        // 認証情報の許可
        config.setAllowCredentials(true);
        
        // プリフライトのキャッシュ時間(秒)
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);

        return new CorsFilter(source);
    }
}

Spring Securityと組み合わせる場合

Spring Securityを使用している場合は、Security設定でもCORSを有効にする必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // CORSを有効化(上記のCorsFilterまたはWebMvcConfigurerの設定を使用)
            .cors(cors -> cors.configure(http))
            // CSRFはAPIサーバーでは通常無効化
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}

開発環境でのプロキシ設定

開発環境ではCORSエラーを回避するために、プロキシを設定する方法が有効です。フロントエンドの開発サーバーがAPIリクエストを中継することで、ブラウザから見ると同一オリジンへのリクエストとなり、CORSの制約を受けなくなります。

プロキシを使用するメリット

プロキシを使用することで、以下のメリットがあります。

  1. 本番環境と異なるCORS設定が不要: 開発用に緩いCORS設定をする必要がない
  2. APIサーバーの変更不要: バックエンド側の設定を変更せずにフロントエンド開発が可能
  3. 環境差異の最小化: 本番環境に近い状態でテストできる
sequenceDiagram
    participant B as ブラウザ
    participant D as 開発サーバー<br/>(localhost:5173)
    participant A as APIサーバー<br/>(api.example.com)
    
    B->>D: /api/users (同一オリジン)
    Note over B,D: ブラウザから見ると<br/>同一オリジンへのリクエスト
    D->>A: api.example.com/users (サーバー間通信)
    A-->>D: レスポンス
    D-->>B: レスポンス
    Note over B,A: CORSエラーは発生しない

Vite(Vue, React, Svelte)でのプロキシ設定

Viteを使用したプロジェクトでは、vite.config.js でプロキシを設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';  // または vue() など

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      // /apiで始まるリクエストをAPIサーバーに転送
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        // パスの書き換え(必要に応じて)
        // rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

この設定により、フロントエンドコードでは以下のようにAPIを呼び出せます。

1
2
3
// 開発環境ではプロキシ経由でリクエスト
const response = await fetch('/api/users');
const data = await response.json();

開発サーバーが https://api.example.com/users に対してリクエストを転送します。

Create React App(webpack)でのプロキシ設定

Create React Appを使用したプロジェクトでは、package.json または setupProxy.js でプロキシを設定できます。

package.jsonでの簡易設定

シンプルなプロキシ設定は package.json に追加できます。

1
2
3
4
5
{
  "name": "my-app",
  "version": "0.1.0",
  "proxy": "https://api.example.com"
}

この設定により、認識されないリクエスト(静的ファイル以外)はすべて指定したURLに転送されます。

setupProxy.jsでの詳細設定

より詳細な設定が必要な場合は、src/setupProxy.js を作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'https://api.example.com',
      changeOrigin: true,
      pathRewrite: {
        '^/api': '/api'  // パスの書き換え(必要に応じて)
      }
    })
  );
};

Next.jsでのプロキシ設定(Rewrites)

Next.jsでは、next.config.jsrewrites 機能を使用してAPIリクエストをプロキシできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.example.com/api/:path*'
      }
    ];
  }
};

module.exports = nextConfig;

この設定により、/api/users へのリクエストは https://api.example.com/api/users に転送されます。

Angular CLI(webpack)でのプロキシ設定

Angular CLIでは、proxy.conf.json を作成してプロキシを設定します。

1
2
3
4
5
6
7
8
// proxy.conf.json
{
  "/api": {
    "target": "https://api.example.com",
    "secure": true,
    "changeOrigin": true
  }
}

開発サーバーを起動する際にプロキシ設定ファイルを指定します。

1
ng serve --proxy-config proxy.conf.json

または、angular.json に設定を追加することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "projects": {
    "my-app": {
      "architect": {
        "serve": {
          "options": {
            "proxyConfig": "proxy.conf.json"
          }
        }
      }
    }
  }
}

CORSエラーのトラブルシューティング手順

CORSエラーに遭遇した際の体系的なトラブルシューティング手順を紹介します。

ステップ1: エラーメッセージを確認する

ブラウザの開発者ツール(DevTools)のコンソールタブで、エラーメッセージを確認します。エラーメッセージには原因が明記されていることが多いです。

ステップ2: ネットワークタブでリクエストを確認する

DevToolsのネットワークタブで、該当のリクエストを確認します。確認すべきポイントは以下のとおりです。

  • リクエストヘッダー: Origin ヘッダーが正しく設定されているか
  • レスポンスヘッダー: Access-Control-Allow-* 系のヘッダーが返されているか
  • OPTIONSリクエスト: プリフライトリクエストが成功しているか

ステップ3: プリフライトリクエストを確認する

OPTIONSリクエストが送信されている場合は、そのレスポンスを確認します。

1
2
3
4
5
6
確認項目

1. ステータスコードは200または204か
2. Access-Control-Allow-Origin は正しいオリジンを返しているか
3. Access-Control-Allow-Methods に必要なメソッドが含まれているか
4. Access-Control-Allow-Headers に必要なヘッダーが含まれているか

ステップ4: サーバーログを確認する

サーバー側でリクエストを受信しているか、エラーが発生していないかを確認します。

ステップ5: 一時的な回避策でテストする

問題の切り分けのため、以下の一時的な回避策を試すことも有効です。

  • ブラウザ拡張機能: CORS制約を無効化する拡張機能(開発時のみ使用)
  • curlコマンド: ブラウザを介さずにAPIをテスト
  • Postman/Insomnia: APIクライアントツールでテスト
1
2
3
4
# curlでAPIをテスト(CORSはブラウザの機能なのでcurlでは発生しない)
curl -X GET https://api.example.com/users \
  -H "Origin: https://app.example.com" \
  -v

セキュリティに関する注意点

CORS設定を行う際は、セキュリティを考慮することが重要です。

本番環境での推奨設定

  1. ワイルドカードを避ける: Access-Control-Allow-Origin: * は本番環境では使用しない
  2. 許可リストを明示する: 許可するオリジンを明示的にリストアップする
  3. 認証情報には注意: credentials: true を使用する場合は特に慎重に設定する
  4. 必要最小限のメソッドとヘッダー: 必要なものだけを許可する

避けるべきアンチパターン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 悪い例: すべてを許可
app.use(cors({
  origin: '*',
  credentials: true  // ワイルドカードとcredentialsの組み合わせはエラーになる
}));

// 悪い例: Originヘッダーをそのまま返す(オープンリダイレクト攻撃のリスク)
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', req.headers.origin);
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
});

まとめ

この記事では、CORSエラーの解決パターンと各種フレームワークでの実装例について解説しました。

主要なポイントをまとめると以下のとおりです。

  • CORSエラーのパターン: エラーメッセージから原因を特定し、適切なヘッダーを設定する
  • サーバーサイド設定: Express、Django、Spring Bootそれぞれで適切なCORS設定を行う
  • 開発環境のプロキシ: Vite、CRA、Next.js、Angularでプロキシを設定してCORSを回避する
  • セキュリティ: 本番環境では許可するオリジンを明示的に指定し、ワイルドカードを避ける

CORSエラーは一見複雑に見えますが、仕組みを理解すれば体系的に対処できます。この記事の内容を参考に、CORSエラーに遭遇した際も迅速に解決できるようになることを願っています。

参考リンク