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 の単純な呼び出し以外(例: PUT、DELETE、Authorization付き)では、ブラウザが本リクエスト前に 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 Gateway | API全体で共通のCORSポリシーを統一 | ルートごとに差分設定すると破綻しやすい |
| ALB | 入口のルーティング・TLS終端 | CORS制御を主担当にすると構成が複雑化しやすい |
| Nginx | 入口での共通ヘッダー付与、OPTIONS短絡応答 | アプリ側設定と二重化すると競合しやすい |
| アプリ本体 | エンドポイント単位の細かいCORS制御 | 複数サービスで実装が散らばると不整合が出やすい |
よくある失敗パターン
OPTIONSはNginx、GET/POSTはアプリで別々に設定している- 成功レスポンスはGateway、エラーレスポンスはアプリが返している
- ステージ/環境ごとに許可オリジンの定義場所が違う
この状態だと、ブラウザから見ると「たまに通る、たまに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では次を確認します。
- Originヘッダーをオリジンに転送する(Origin Request Policy)
- 必要なら Response Headers Policy でCORSヘッダーを付与する
- キャッシュ誤配信を防ぐため、必要に応じて
Originをキャッシュキーに含める
特に複数オリジン(本番/ステージング)を許可する場合、Origin を考慮しないキャッシュ設定だと、別オリジン向けヘッダーが混ざることがあります。
CloudFrontの具体設定例(実務向け)
ここでは https://app.example.com から https://assets.example.com(CloudFront)上の画像/JSONを読むケースを想定します。
ケース1: GET/HEADのみ(最小構成)
- CloudFrontの対象ビヘイビアで
GET, HEADを許可 - Origin Request Policyで
Originヘッダーをオリジン(S3)へ転送 - 複数オリジンを許可するなら、Cache Policyで
Originをキャッシュキーに含める - Response Headers Policyは「S3のCORSをそのまま返す」か「CloudFrontで固定付与」のどちらかに統一(両方で二重管理しない)
ケース2: preflightあり(PUT/DELETE/カスタムヘッダーあり)
- ビヘイビアの許可メソッドに
OPTIONSを含める - Origin Request Policyで次の3ヘッダーを転送する
OriginAccess-Control-Request-MethodAccess-Control-Request-Headers
- preflightレスポンス(
OPTIONS)にもCORSヘッダーが返るようにする 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-* ヘッダーの具体的な設計に進むと理解が深まります。 -->