misc4分で読める

>Browser: 200 + Access-Control-Allow-Origin: https://app.example.com
Browser-->>App: レスポンス参照を許可

Note over Browser,Api: 攻撃系(読み取りを防ぐ)
User->>Browser: evil.example.net を開く
Evil->>Browser: fetch("https://api.example.com/me")
Browser->>Api: GET /me + Origin: https://evil.example.net
Api-->>Browser: 200(または4xx)+ 許可なし
Browser-->>Evil: JSからのレスポンス参照を遮断

---

## CORSの基本動作

ブラウザはクロスオリジンのリクエスト時に、サーバーのレスポンスヘッダーを見て「この通信を読み取ってよいか」を判断します。

代表的なのは `Access-Control-Allow-Origin` です。

```http
Access-Control-Allow-Origin: https://app.example.com

このヘッダーが適切に返されると、ブラウザはJavaScriptからレスポンスを参照できるようになります。 逆に許可されていない場合、通信自体はサーバーに届いていても、ブラウザ側で結果の参照が拒否されることがあります。

図解: CORSの基本フロー


Access-Control-Allow-Methods

Access-Control-Allow-Method と書かれることがありますが、正式なレスポンスヘッダー名は Access-Control-Allow-Methods(複数形)です。

このヘッダーは「このオリジンから、どのHTTPメソッドを許可するか」をブラウザに伝えます。

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

たとえばフロントが PUT を使うのに、サーバー側が GET, POST しか返さない場合、preflight段階でブラウザに拒否されます。 また、OPTIONS を許可に含めていないと、preflightへの応答自体が失敗しやすくなります。


Access-Control-Allow-Headers

Access-Control-Allow-Headers は、クライアントが送ってよいリクエストヘッダーを示します。 ブラウザはpreflightで Access-Control-Request-Headers を送り、サーバーがそれを許可しているか照合します。

Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id

特に Authorization や独自ヘッダー(X-*)を使うときは、ここに明示しないとpreflightで失敗しやすいです。

Access-Control-Request-Headers: authorization, x-request-id
Access-Control-Allow-Headers: Authorization, X-Request-Id

OPTIONS(preflight)とは

GET の単純な呼び出し以外(例: PUTDELETEAuthorization付き)では、ブラウザが本リクエスト前に OPTIONS を送ることがあります。これが preflight です。

preflightで確認される主な項目:

  • その Origin を許可しているか
  • 要求された Method を許可しているか
  • 要求された Headers を許可しているか

サーバーがこの確認に正しく応答できないと、本リクエスト(POST など)まで到達しません。

preflight


4xx/5xx のときのCORS注意点

CORS実装でよくある落とし穴は、正常系(2xx)だけCORSヘッダーを付けて、4xx/5xxには付けないことです。

この状態だと、APIはエラーを返していてもブラウザ側では「CORSエラー」に見え、フロントから本来のエラー本文を読めません。

実務では次を徹底するとデバッグが楽になります。

  • 2xxだけでなく 4xx/5xxにもCORSヘッダーを付与する
  • OPTIONS のレスポンスにも同様のCORSヘッダーを返す
  • API Gateway / ALB / Nginx / アプリ本体のどこでヘッダーを付けるかを統一する

例(4xxでもCORSヘッダーは返す):

HTTP/1.1 401 Unauthorized
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"message":"token is invalid"}

API Gateway / ALB / Nginx / アプリ本体のどこでCORSヘッダーを付けるべきか

「どこで付けるかを統一する」の意味は、付与責任を1レイヤに寄せるということです。 複数レイヤでバラバラに設定すると、環境ごとに挙動が変わり、障害時の切り分けが難しくなります。

まず決めるべき方針

  • 原則: CORSは1か所で一元管理する
  • 例外: 例外が必要なら「どのパスだけ別設定か」を明文化する
  • 運用: 2xx / 4xx / 5xx / OPTIONS の全パターンで同じポリシーを返す

レイヤ別の役割

レイヤ向いている役割注意点
API GatewayAPI全体で共通のCORSポリシーを統一ルートごとに差分設定すると破綻しやすい
ALB入口のルーティング・TLS終端CORS制御を主担当にすると構成が複雑化しやすい
Nginx入口での共通ヘッダー付与、OPTIONS短絡応答アプリ側設定と二重化すると競合しやすい
アプリ本体エンドポイント単位の細かいCORS制御複数サービスで実装が散らばると不整合が出やすい

よくある失敗パターン

  1. OPTIONS はNginx、GET/POST はアプリで別々に設定している
  2. 成功レスポンスはGateway、エラーレスポンスはアプリが返している
  3. ステージ/環境ごとに許可オリジンの定義場所が違う

この状態だと、ブラウザから見ると「たまに通る、たまにCORSエラー」になりやすいです。

実務での推奨パターン

パターンA: API Gatewayで一元管理(マネージド寄り)

  • 向いているケース: サーバレス中心、APIの入口をGatewayに寄せている
  • メリット: 設定の見通しが良い、全APIへ同一ポリシーを適用しやすい
  • 注意: バックエンドが返すエラーレスポンスにも最終的に同一方針が反映されるか確認する

パターンB: Nginxで一元管理(リバースプロキシ寄り)

  • 向いているケース: ECS/EC2でNginxを入口にしている
  • メリット: 複数アプリを跨いで同じCORSヘッダーを強制しやすい
  • 注意: アプリ側のCORSミドルウェアは無効化または最小化して二重管理を避ける

パターンC: アプリ本体で一元管理(アプリ寄り)

  • 向いているケース: エンドポイントごとに許可オリジン/ヘッダーを細かく変えたい
  • メリット: ビジネスロジックに近い場所で柔軟に制御できる
  • 注意: すべてのエラーハンドラと OPTIONS 処理で同一ヘッダーを返す実装が必要

CloudFront / S3 のCORS設定

静的サイトや画像配信では、CloudFront + S3 構成でもCORS設定が必要です。 ここで重要なのは、CloudFrontとS3で役割が違うことです。

  • S3: オリジンとして「どのOrigin/Method/Headerを許可するか」を定義する
  • CloudFront: ヘッダーを中継・付与しつつ、キャッシュキー設計でCORSの整合性を保つ

図解: CloudFrontとS3の役割分担

S3側のCORS(バケット設定)

S3はバケット単位でCORSルールを持ちます。例:

[
  {
    "AllowedOrigins": ["https://app.example.com"],
    "AllowedMethods": ["GET", "HEAD", "PUT"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

ポイント:

  • 開発時でも AllowedOrigins: ["*"] の常用は避ける
  • PUT/POST を使うなら、必要メソッドを明示する
  • 署名付きアップロードで独自ヘッダーを使う場合、AllowedHeaders を不足なく設定する

CloudFront側での注意

CloudFrontでは次を確認します。

  1. Originヘッダーをオリジンに転送する(Origin Request Policy)
  2. 必要なら Response Headers Policy でCORSヘッダーを付与する
  3. キャッシュ誤配信を防ぐため、必要に応じて Origin をキャッシュキーに含める

特に複数オリジン(本番/ステージング)を許可する場合、Origin を考慮しないキャッシュ設定だと、別オリジン向けヘッダーが混ざることがあります。

CloudFrontの具体設定例(実務向け)

ここでは https://app.example.com から https://assets.example.com(CloudFront)上の画像/JSONを読むケースを想定します。

ケース1: GET/HEADのみ(最小構成)

  1. CloudFrontの対象ビヘイビアで GET, HEAD を許可
  2. Origin Request Policyで Origin ヘッダーをオリジン(S3)へ転送
  3. 複数オリジンを許可するなら、Cache Policyで Origin をキャッシュキーに含める
  4. Response Headers Policyは「S3のCORSをそのまま返す」か「CloudFrontで固定付与」のどちらかに統一(両方で二重管理しない)

ケース2: preflightあり(PUT/DELETE/カスタムヘッダーあり)

  1. ビヘイビアの許可メソッドに OPTIONS を含める
  2. Origin Request Policyで次の3ヘッダーを転送する
    • Origin
    • Access-Control-Request-Method
    • Access-Control-Request-Headers
  3. preflightレスポンス(OPTIONS)にもCORSヘッダーが返るようにする
  4. OPTIONS をキャッシュする場合はTTLを短めに設計し、設定変更反映を遅らせない

補足:

  • CloudFrontでCORSヘッダーを上書きする場合、S3側CORSと矛盾しない値にそろえる
  • 「S3で許可」「CloudFrontで拒否」またはその逆が起きると、原因切り分けが難しくなる

よくあるハマりどころ(CloudFront + S3)

  • S3のCORSは正しいが、CloudFrontが Origin を転送しておらず判定が崩れる
  • OPTIONS をビヘイビアで許可しておらず、preflightが失敗する
  • CloudFrontでヘッダーを上書きして、S3の意図したCORS設定と食い違う

「S3だけ設定したからOK」ではなく、CloudFrontの転送・キャッシュ・ヘッダー付与までセットで設計するのが実務では重要です。

チェックリスト(これを満たせば事故が減る)

  • OPTIONS がすべての対象パスで200/204を返す
  • Access-Control-Allow-Origin が環境ごとに意図通り(* 乱用なし)
  • Access-Control-Allow-Methods と実際の利用メソッドが一致
  • Access-Control-Allow-Headers と実際の要求ヘッダーが一致
  • 2xx / 4xx / 5xx の全レスポンスに同じCORS方針が適用される

まず押さえるべきポイント

  • CORSはブラウザの制約であり、サーバー間通信(backend-to-backend)には基本的に関係しない
  • CORSは認証・認可の代わりではない(APIのアクセス制御は別で必要)
  • *(ワイルドカード)許可は便利だが、認証付き通信では使えない条件がある

まとめ

CORSは、同一オリジンポリシーを壊さずに、必要なクロスオリジン通信だけを安全に許可するための仕組みです。 最初は「ブラウザがヘッダーを見て可否を決めるルール」と理解すると、実装やデバッグがかなり楽になります。

次は、preflight(OPTIONSリクエスト)と Access-Control-Allow-* ヘッダーの具体的な設計に進むと理解が深まります。 -->

RK

1997年生まれ

ITエンジニア

インフラ・SRE