1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】Simple Request と Preflight の整理

1
Posted at

はじめに

CORS でつまずく原因の 8 割はこれです:

  • 「なぜいきなり OPTIONS が飛ぶの?」
  • 「GET なのに CORS エラー?」
  • 「curl では通るのに、ブラウザだけ死ぬ」

答えはシンプルで、ブラウザはクロスオリジン通信を 2種類 に分けています。

  • Simple Request(事前確認なし)
  • Preflight Request(事前確認=OPTIONS あり)

この記事ではこの2つを、ルール・判定・実例・デバッグ視点で“完全整理”します。


大前提:CORS は「ブラウザの読み取り制御」

  • サーバーがレスポンスを返しても
  • ブラウザが “その中身をJSに渡さない” ことがある

つまり CORS エラーは「通信失敗」ではなく “読み取り拒否” です。


1. Simple Request とは

Simple Request = ブラウザが危険度低いと判断し、事前確認なしで本リクエストを送るもの

Simple Request の条件(全部満たす必要あり)

HTTPメソッドが次のいずれか

  • GET
  • POST
  • HEAD

“手動で付けるリクエストヘッダ” が制限内(代表例)

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type(ただし次の3つのいずれかに限る)
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

“特殊なヘッダ” を付けていない

例:Authorization, X-API-Key, X-Requested-With などを 付けた瞬間にアウト(=Preflight)


2. Preflight とは

Preflight =「このリクエスト、送っていい?」を先に OPTIONS で確認する仕組み

ブラウザが先にこう聞きます:

https://client.example から PUT で、Authorization 付きで送るけど、いい?」

Preflight が発生する代表条件(どれか1つでも該当)

  • メソッドが PUT / PATCH / DELETE など(GET/POST/HEAD以外)
  • Content-Type: application/json(←これ超多い)
  • AuthorizationX-* などのカスタムヘッダ
  • fetch()credentials: "include" を使う場合も設計次第で要注意

3. 実例で“発動条件”を体に染み込ませる

例A:Simple(GET)

fetch("https://api.example.com/users");

GET で余計なヘッダも無し
Preflightなし


例B:Preflight(POST + JSON)

fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice" })
});

Content-Type: application/json がアウト
Preflight発生


例C:Preflight(Authorization)

fetch("https://api.example.com/me", {
  headers: { "Authorization": "Bearer xxx" }
});

Authorization はシンプルヘッダじゃない
Preflight発生


例D:Preflight(PUT)

fetch("https://api.example.com/users/1", {
  method: "PUT",
  headers: { "Content-Type": "text/plain" },
  body: "hello"
});

PUT は Simple の対象外
Preflight発生


4. Preflight の実際の通信フロー

ブラウザ内部ではこうなります。

ポイントはここ:

  • OPTIONS が 通らない と、本リクエストは 送られない
  • OPTIONS が通っても、本リクエストのレスポンスに CORS ヘッダが無いと 読めない

5. サーバーが返すべきヘッダ(最小セット)

Simple Request を読ませたいだけなら

レスポンスにこれが必要:

Access-Control-Allow-Origin: https://client.example

Preflight を通したいなら(OPTIONS のレスポンスに)

Access-Control-Allow-Origin: https://client.example
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600

そして 本リクエストのレスポンスにも Access-Control-Allow-Origin は必要です。
(ここ忘れて「OPTIONS は通るのに CORS エラー」になる人、多い)


6. “Simple なのに CORS エラー”の典型パターン

パターン1:サーバーは 200 を返してるのに JS が読めない

Access-Control-Allow-Origin が無い/合ってない

パターン2:リダイレクトが挟まる

  • 301/302 先のレスポンスに CORS ヘッダが無い
  • あるいは途中で Origin が変わる

パターン3:CDN / Proxy がヘッダを落としてる

  • 返してるつもりでも、途中で消される

7. “即判断”チェックリスト

次のどれかがあれば Preflight確定

  • Content-Type: application/json
  • Authorization を付ける
  • X-... のヘッダを付ける
  • メソッドが PUT / PATCH / DELETE
  • ブラウザが「なんか怖い」と感じる構成(リダイレクト多、特殊ヘッダ等)

逆に Simple はだいたいこれ:

  • GET / HEAD
  • POST でも text/plain or form-urlencoded で余計なヘッダなし

8. よくある地雷:Allow-Origin と Credentials の組み合わせ

覚え方はこれ:

Cookie を跨いで送る(credentials)なら、Allow-Origin は \* にできない

つまりこれは 禁止

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

まとめ

  • Simple Request:条件が厳しい代わりに、事前確認なし
  • Preflight:条件を外れると OPTIONS で許可を取りに行く
  • 実務で一番多い Preflight の原因は
    • application/json
    • Authorization

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?