Interactive

CORSプリフライト判定可視化

「いつOPTIONSが飛ぶのか」は気分ではなく、Fetch標準の厳密な判定で決まっています。OPTIONSは飛ぶかタブではメソッド・ヘッダ・Content-Typeを実際に判定にかけ、どの条件で発火したかを示します。サーバー応答との照合タブではAccess-Control-*を編集して、どの段で照合が落ちたかを確認できます。

シナリオ

記事の「GETは通るのにPOSTだけ落ちる」の正体。メソッドは safelisted なのに Content-Type だけで発火する。

リクエストヘッダ

✗ application/json は許可された3種のいずれでもない

safelistedヘッダの値の合計: 0 / 1024 バイト

OPTIONS が先行する(プリフライトあり)

本番リクエストはOPTIONSが許可されるまで一度も送られない。

判定の内訳

0クロスオリジンか判定対象

https://app.example.com → https://api.example.com で別オリジン。CORSの判定対象。

1メソッドが GET / HEAD / POST✓ 満たす

POST は safelisted method

2ヘッダが CORS-safelisted のみ✗ 外れている

content-type が safelisted でない

Content-Type が safelisted になるのはapplication/x-www-form-urlencodedmultipart/form-datatext/plainの3種のときだけ。値の長さも1つあたり128バイトまで。

ブラウザが送るOPTIONS

OPTIONS /items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

申告するヘッダ名は小文字に正規化され、ソートされて送られる(記述順は影響しない)。

判定は Fetch 標準の手続きをそのまま実装したもので、ブラウザ実装ごとの差異(max-ageの上限など)は明示している箇所のみ反映しています。

ここが分かる

  • POSTは常にプリフライトされる、は誤り——POST自体はsafelistedなメソッドで、発火させているのはContent-Type: application/jsonのほう。同じPOSTでもtext/plainに変えるとOPTIONSが消えるのを実際に確認できる。
  • 資格情報付きでは*が記号になる——Allow-OriginだけでなくAllow-MethodsAllow-Headersも同時にワイルドカードの意味を失う。credentialsを切り替えると、同じサーバー応答が成功と失敗を行き来する。
  • AuthorizationAllow-Headers: *では絶対に通らない——資格情報を送っていなくても、この1つだけは名前を明示する必要がある(non-wildcardヘッダ)。同じ*X-Trace-Idは通るのと対比できる。
  • プリフライトが通っても「メソッドが許可された」とは限らない——GET/HEAD/POSTはsafelistedなので、Allow-Methodsに書かれていなくてもメソッドの照合自体は通る。Allow-Methods: GETだけの応答でJSONのPOSTが通ってしまう様子を確認できる。
  • キャッシュキーはオリジン・URL・メソッド・申告ヘッダ集合・資格情報モードの組。独自ヘッダを1つ足しただけでOPTIONSが復活する理由が、キーの変化として見える。