DevToolBox

CORSエラーの原因と正しい直し方 - preflight・credentials まで整理

最終更新日: 2026年4月19日

Access to fetch at '...' from origin '...' has been blocked by CORS policyというエラーに当たったとき、クライアント側のfetchオプションをいじっても解決しないのが普通です。CORSはブラウザが安全のため課す「同一生成元ポリシー」の例外ルールで、 許可の判定は サーバが返すレスポンスヘッダで行います。 この記事ではCORSの仕組み、よくあるエラーの読み方、Nginx・Expressでの正しい設定、 やってはいけない対処を整理します。

When a blocked by CORS policy error shows up, tweaking fetchoptions on the client will not fix it. CORS is enforced by the browser based onserver-side response headers. This guide walks through how to read the error, the simple-vs-preflight boundary, correct Express and Nginx configs, credentials pitfalls, and common anti-patterns.

図1: 単純リクエストとpreflightの違い

Browserorigin AAPI Serverorigin B■ 単純リクエスト (GET, POST text/plain 等)GET /api200 + Access-Control-Allow-Origin■ Preflight (PUT/DELETE, Content-Type:application/json 等)OPTIONS /api (事前確認)204 + Allow-Methods, Allow-Headers本リクエスト (PUT /api)200 + Allow-Origin

1. エラーメッセージの読み方

主要なエラーパターンと、どこを直すべきかの対応表です。

エラーの一部意味直す場所
No 'Access-Control-Allow-Origin'ヘッダ自体がないサーバ
has been blocked by CORS policy: Response to preflightOPTIONSが200/204を返していないサーバ / プロキシ
The value of the 'Access-Control-Allow-Credentials' header in the response is ''credentials:include だが許可ヘッダが無いサーバ
Request header field X-... is not allowed by Access-Control-Allow-Headersカスタムヘッダが許可されていないサーバ
Cannot use wildcard ... when credentials mode is 'include'credentials時は * NGサーバ

ほぼ全ての原因は サーバ側のレスポンスヘッダ不足です。 クライアントで mode:'no-cors' を付けても、レスポンスが不透明 (opaque) になって 中身を読めなくなるだけで、根本解決にはなりません。

2. 単純リクエストとpreflightの境界

ブラウザは「副作用が少なく昔からあった形のリクエスト」だけを単純リクエストとして 即送信し、それ以外は OPTIONS で事前確認します。単純リクエストの条件は:

application/jsonAuthorization: Bearer を付けた時点でほぼ全てのAPI呼び出しがpreflightを伴うことになります。 なので「GETなら通るがPOST/PUTで落ちる」というときは、まず OPTIONS の応答を ブラウザDevTools → Network で確認します。

3. 正しいサーバ設定(Express)

// Express + cors パッケージ(推奨)
const cors = require("cors");

app.use(cors({
  origin: (origin, cb) => {
    const allow = ["https://app.example.com", "https://admin.example.com"];
    // origin が undefined の場合 = curl等 → 許可したくなければ false
    if (!origin || allow.includes(origin)) return cb(null, true);
    return cb(new Error("Not allowed by CORS"));
  },
  credentials: true,           // Cookie/Authorization を送る場合
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization", "X-Request-Id"],
  maxAge: 86400,               // preflight結果のキャッシュ(秒)
}));

ここでハマりやすいのが credentials: true のときは origin: "*" が使えないという点です。CORS仕様上、Cookieを送るなら必ず 個別ドメインを明示する必要があります。

4. 正しいサーバ設定(Nginx)

# Nginx での例(API GWや静的配信前段)
location /api/ {
    # 単純リクエスト+preflight 両対応
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "$http_origin" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Request-Id" always;
        add_header Access-Control-Allow-Credentials "true" always;
        add_header Access-Control-Max-Age 86400 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin "$http_origin" always;
    add_header Access-Control-Allow-Credentials "true" always;

    proxy_pass http://backend;
}

add_header常にレスポンスに必要なので always を忘れずに。 502/504等のエラー応答でヘッダが消えると、そのエラー画面もCORSでブロックされて 真因が分からなくなります。

5. credentials (Cookie) を使うときの3つの条件

SameSite属性の影響で、さらにCookieには SameSite=None; Secureが必要です(Chrome 80以降)。サブドメイン間共有なら SameSite=Lax で十分ですが、 完全なクロスサイトでは None; Secure を使い、HTTPS必須です。

6. よくある誤った対処

7. 開発中のワークアラウンド

本番のサーバ設定が直せない場合の一時しのぎ:

いずれも 本番でバックエンドチームと正しい設定を詰めるまでの暫定手段 と割り切ること。

8. よくある質問

Q. なぜブラウザだけCORSがあり、curlでは無いのか?

CORSはブラウザのJavaScriptが勝手に他オリジンのリソースを読み取れないよう守る仕組みです。curlやサーバ同士の通信は、そもそもユーザのCookieやセッションを流用できないので 同じ脅威モデルがありません。

Q. プリフライトが毎回飛んでAPIが遅い

Access-Control-Max-Age を大きめ (86400 = 24時間) に設定すると、 同じエンドポイント・メソッド・ヘッダの組合せについてブラウザがキャッシュします。

Q. 社内ツールは全部 * で問題ないのでは?

Cookie や Authorization を使わず、公開情報だけ返すAPIなら * でも実害は小さい ですが、内部APIが 他のイントラネットサイトから読み取れる状態になります。 基本はオリジンホワイトリスト方式を推奨します。


English summary

A CORS policy error is emitted by the browser, not the server. The server must return the right headers on the actual response and the preflight (OPTIONS) response. Remember three rules: (1) any request withapplication/json or Authorization triggers preflight; (2) when using credentials: "include", Allow-Origin must be an explicit origin (not *) and Allow-Credentials: true is required; (3) in Nginx, always use the always flag on add_header so error responses (502/504) still carry the CORS headers — otherwise the real error is masked.

関連ツール・ガイド

参考: MDN - オリジン間リソース共有 (CORS) / WHATWG Fetch Standard