4.

CORS エラー (Access blocked) の原因と対処法完全ガイド

編集
この記事の要点
  • CORS (Cross-Origin Resource Sharing) エラーはブラウザが Same-Origin Policy により別オリジンへの fetch/XHR をブロックしたときに出る
  • オリジン = scheme + host + port の組。http/httpslocalhost:3000/localhost:8080 は別オリジン
  • 対処はサーバ側で許可ヘッダを返す: Access-Control-Allow-Origin、必要に応じて -Methods / -Headers / -Credentials
  • Laravel は config/cors.php、Spring は @CrossOrigin、Express は cors ミドルウェア、Nginx は add_header
  • 開発中は Vite proxy / webpack-dev-server proxy で回避するのが簡単

エラーの全文例

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

# preflight が失敗するケース
Access to fetch at 'https://api.example.com/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass
access control check: It does not have HTTP ok status.

# credentials 付きのケース
Access to XMLHttpRequest at 'https://api.example.com/login' from origin
'https://app.example.com' has been blocked by CORS policy: The value of the
'Access-Control-Allow-Credentials' header in the response is '' which must
be 'true' when the request's credentials mode is 'include'.

CORS の仕組み

ブラウザは Same-Origin Policy により、別オリジンへの XHR / fetch をデフォルトでブロックします。CORS はサーバが「許可する」と明示することでこれを緩和する仕組みです。

項目説明
オリジンscheme + host + port (例: https://example.com:443)
Same-Originscheme / host / port がすべて一致
Cross-Origin1 つでも違えば別オリジン
Simple RequestGET / HEAD / POST かつ Content-Type 限定。preflight 不要
Preflight RequestPUT / DELETE / PATCH / JSON POST 等で OPTIONS が事前送信される
Credentialscookie / Authorization ヘッダを含めるか

必要な CORS レスポンスヘッダ

ヘッダ用途
Access-Control-Allow-Origin許可するオリジンhttps://app.example.com または *
Access-Control-Allow-Methods許可する HTTP メソッドGET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers許可するリクエストヘッダContent-Type, Authorization, X-Requested-With
Access-Control-Allow-Credentialscookie 送信許可true
Access-Control-Max-Agepreflight キャッシュ秒数86400
Access-Control-Expose-HeadersJS から読み取り可能ヘッダX-Total-Count

対処1: Laravel

Laravel 7+ は fruitcake/laravel-cors 同梱で config/cors.php 編集だけで OK:

// config/cors.php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'],
    'allowed_methods' => ['*'],
    'allowed_origins' => [
        'http://localhost:3000',
        'http://localhost:5173',
        'https://app.example.com',
    ],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,   // ★ Sanctum 利用時は true
];

// 設定キャッシュをクリア
// php artisan config:clear

対処2: Spring Boot (Java)

// コントローラに付与
@RestController
@CrossOrigin(origins = "https://app.example.com",
             methods = {RequestMethod.GET, RequestMethod.POST},
             allowCredentials = "true")
public class UserController {
    @GetMapping("/users")
    public List list() { ... }
}

// グローバル設定
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://app.example.com",
                                "http://localhost:5173")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

対処3: Express (Node.js)

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

// 簡易: 全許可 (開発用)
app.use(cors());

// 本番: 明示許可
app.use(cors({
  origin: ['https://app.example.com', 'http://localhost:5173'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  maxAge: 86400,
}));

// ルート単位
app.get('/users', cors(), (req, res) => { ... });

対処4: Nginx でヘッダ追加

server {
    listen 443 ssl;
    server_name api.example.com;

    location / {
        # preflight (OPTIONS) 応答
        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-Allow-Credentials' 'true';
            add_header 'Access-Control-Max-Age' 86400;
            return 204;
        }

        # 通常リクエスト応答
        add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;

        proxy_pass http://backend;
    }
}

対処5: 開発時はプロキシで回避

本番と同じ CORS 設定を毎回書くのが面倒な場合、開発サーバのプロキシ機能を使うとブラウザから見て同一オリジンになります:

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',  // Laravel など
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '/api'),
      },
    },
  },
});

// Vue 側からは /api/users と書けば
// 実際は http://localhost:8000/api/users へプロキシ
// ブラウザ目線では同一オリジンなので CORS 不要
// webpack-dev-server (Vue CLI / Nuxt 2)
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
    },
  },
};

// Next.js next.config.js
module.exports = {
  async rewrites() {
    return [{ source: '/api/:path*', destination: 'http://localhost:8000/api/:path*' }];
  },
};

credentials: include 利用時の注意

cookie や Authorization ヘッダを送る場合、サーバは credentials を許可し、Origin を 具体値 (* 不可) で返す必要があります:

// クライアント (axios)
const api = axios.create({
  baseURL: 'https://api.example.com',
  withCredentials: true,   // ★ cookie 送る
});

// サーバ
// Access-Control-Allow-Origin: https://app.example.com  ★ * は不可
// Access-Control-Allow-Credentials: true               ★ 必須

preflight (OPTIONS) でつまずくパターン

  • POST + Content-Type: application/json → preflight 発火
  • Authorization ヘッダ付き → preflight 発火
  • カスタムヘッダ (X-Custom-*) → preflight 発火
  • サーバが OPTIONS を 404 / 405 で返すと CORS エラー
  • Nginx で OPTIONS をバックエンドに転送し忘れる

FAQ

Q: Access-Control-Allow-Origin: * でいいのでは?
A: 開発や公開 API は OK。ただし credentials: true とは併用できない。本番は具体値を指定。

Q: フロントエンドの修正で CORS を直せる?
A: 不可能。CORS はサーバが許可する仕組み。開発時はプロキシで回避。

Q: Postman では成功するのにブラウザでは失敗する
A: Postman は CORS を無視する。ブラウザだけが Same-Origin Policy を強制。サーバ側の設定が原因。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Uncaught TypeError: Illegal invocation
  2. Form submission canceled because the form is not connected
  3. Uncaught TypeError: location.href is not a function
  4. Access to XMLHttpRequest at 'url1' from origin 'url2' has been blocked
  5. Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')