DevToolBox

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

「本番だけ 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.

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. 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.

関連ツール / Related tools

関連ガイド / Related guides