DevToolBox

CORS プリフライトで 400 / 403 が返る原因と対処

最終更新日: 2026-04-19公開日: 2026-04-19執筆: DevToolBox編集部

「本番だけ OPTIONS が 400 / 403 で落ちて API が叩けない」は CORS で最も頻出する 事故です。原因は OPTIONS ルーティング欠落認証ミドルウェアが OPTIONS をブロック必要ヘッダ不足の三択に絞り込めます。

Preflight OPTIONS requests failing with 400/403 almost always come down to one of three causes: missing OPTIONS handler, auth middleware blocking preflight, or missingAccess-Control-Allow-* headers.

HTTP Status Codes

preflightが返した400番台のコードの意味をその場で確認。どのレスポンスが原因かを切り分けるのに使えます。

今すぐ試す →

TL;DR

1. プリフライトが飛ぶ条件 / When preflight fires

2. 原因別の直し方 / Cause table

症状 / Symptom原因 / Cause対処 / Fix
OPTIONS 400ルーティング未定義Express: app.options('*', cors()) 等で明示
OPTIONS 401/403認証ミドルウェアが先に走るOPTIONS を認証より前でバイパス
Missing Allow-HeadersAuthorization をサーバが知らないAccess-Control-Allow-Headers: Authorization, Content-Type
credentials エラーOrigin: * と credentials を併用具体オリジン + Allow-Credentials: true
Chrome: blocked by CORBJSONに Content-Type が無い必ず application/json を返す

3. サーバ別の最小設定 / Minimum server config

Express (Node.js)

import cors from "cors";
app.use(cors({
  origin: "https://app.example.com",
  credentials: true,
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Authorization", "Content-Type"],
}));

Nginx

location /api/ {
  if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization,Content-Type" always;
    add_header Access-Control-Max-Age 86400 always;
    return 204;
  }
  ...
}

4. DevTools での切り分け / DevTools triage

  1. Network タブで失敗中の OPTIONS を選択
  2. Request Headers に Access-Control-Request-Method / -Headers があるか確認
  3. Response Headers にそれぞれに対応する Access-Control-Allow-* が返っているか
  4. 200/204 で返っているのに後続 POST が落ちるなら、POST レスポンスの Allow-Origin 不足

5. 同一オリジンの誤解:ポート・サブドメインも別オリジン / Same-origin misconceptions

「同じドメインなのにCORSエラーが出る」場合、オリジンはスキーム+ホスト+ポートの組で 決まることを見落としているケースがあります。ポートやサブドメイン、httpとhttpsの違いも すべて別オリジン扱いです。

比較対象 / Comparison同一オリジン?
http://example.com:3000 と http://example.com:8080❌ ポートが違う
http://example.com と https://example.com❌ スキームが違う
http://app.example.com と http://example.com❌ サブドメインが違う
http://example.com/a と http://example.com/b✅ パスは無関係、同一オリジン

フロントエンドとバックエンドを別ポートで開発しているとき(例: フロント3000番、API 8080番)は、 本番では同一オリジンに見えても開発環境ではCORSが必要になる、という状況もよく起こります。

6. プリフライトのキャッシュ(Access-Control-Max-Age)の落とし穴 / Preflight caching gotcha

ブラウザはAccess-Control-Max-Ageで指定された秒数だけ、プリフライトの結果を キャッシュします。この間は同じメソッド・ヘッダーの組み合わせに対してOPTIONSを 再送しません。CORS設定を変更した直後にブラウザ側が古いキャッシュを参照し続けて 「直したのに直っていないように見える」ことがあります。

Access-Control-Max-Age: 86400  // 24時間キャッシュされる

// 許可ヘッダーを追加するなど設定変更した直後は、
// ブラウザが古いプリフライト結果をキャッシュしたままになっている可能性がある

検証中はAccess-Control-Max-Ageを短く(あるいは0に)設定するか、DevToolsの Networkタブで「キャッシュを無効化」を有効にして確認すると、設定変更の反映を早く確認できます。

7. Next.js Route HandlersでのCORS設定例 / CORS in Next.js Route Handlers

Next.js App Routerでは、Route HandlerにOPTIONSメソッドを明示的にエクスポートし、 各レスポンスにAccess-Control-Allow-*ヘッダーを付与します。

// app/api/example/route.ts
export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Origin": "https://app.example.com",
      "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
      "Access-Control-Allow-Headers": "Authorization,Content-Type",
    },
  });
}

export async function GET() {
  return Response.json(
    { data: "..." },
    { headers: { "Access-Control-Allow-Origin": "https://app.example.com" } }
  );
}

OPTIONSハンドラーを定義し忘れると、Next.jsのデフォルトルーティングでは 404が返り、本記事冒頭の「ルーティング未定義」と同じ状態になります。

8. English summary

A failing CORS preflight (400/403) is usually a server-side issue, not a browser one. Check three things in order: (1) the server has an OPTIONS route, (2) auth middleware does not reject unauthenticated preflights, and (3) response headers include all requiredAccess-Control-Allow-* values. When using credentials: include, Origin cannot be *; return the specific origin and addAccess-Control-Allow-Credentials: true.

よくある質問 / FAQ

なぜ OPTIONS だけ 400 が返る?
サーバ側で OPTIONS を受けるルーティングが無い、または認証ミドルウェアが OPTIONS もブロックしているケースが大半です。OPTIONS は認証不要で 2xx を返す必要があります。
Why does OPTIONS return 400?
The server typically has no OPTIONS handler or auth middleware is rejecting the unauthenticated preflight. Preflights must return 2xx without requiring credentials.
credentials: include を使うと失敗する
Access-Control-Allow-Origin に * は使えません。具体的なオリジンを返し、Access-Control-Allow-Credentials: true を併記する必要があります。

関連ツール / Related tools

関連ガイド / Related guides