Webアプリケーション開発の現場、特に**「Route53 → CloudFront → ALB → ECS (Fargate)」**といったモダンな構成において、APIの実装中に不可解なエラーに遭遇することがあります。
「ローカル環境やALBへの直接アクセスでは正常に動作するのに、CloudFrontを経由した途端に 403 Forbidden エラーが返ってくる」
このような現象が起きた際、真っ先に疑うのはAWS WAFの誤検知や、S3オリジンの権限設定(OAI/OAC)かもしれません。しかし、もしそのリクエストが**「GETメソッド」**であるなら、原因はもっと根本的な「HTTPリクエストの構造」にある可能性が高いです。
本記事では、バックエンドエンジニアから「APIがCloudFrontで弾かれる」と相談を受けた際に確認すべき、CloudFront特有の仕様と、その解決策について実例を交えて解説します。
APIへのGETリクエストが403 Forbiddenで拒否される
今回のトラブルは、以下のAWS構成でアプリケーション開発を行っている最中に発生しました。
- インフラ構成:Route53 → CloudFront → ALB (Application Load Balancer) → ECS (Fargate)
- 発生状況:バックエンドエンジニアより「APIへGETリクエストを送ると、CloudFrontのエラー画面(403 Forbidden)が返ってくる」との報告。
重要なのは、**「ローカル環境やALBへの直接アクセスでは成功するが、CloudFrontを経由した時だけエラーになる」**という点です。WAF(Web Application Firewall)の誤検知や、S3オリジンの権限設定ミスなどを疑い、CloudFrontのビヘイビア設定を見直しましたが、一向に改善しませんでした。
問題のリクエスト内容
調査のために共有された curl コマンドを確認すると、以下のようなリクエストが送信されていました。
Bash
curl --location --request GET 'https://example.com/api/user/data' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "sample@example.com",
"id": 12345
}'
一見すると一般的なAPIリクエストに見えますが、HTTPメソッドが「GET」であるにもかかわらず、JSON形式の「Body(データ本文)」が含まれていることが分かります。
通常、RESTfulな設計においてGETリクエストはリソースの取得に用いられ、パラメータはクエリ文字列(URLパラメータ)で渡すのが一般的です。しかし、Elasticsearchなどの一部のミドルウェアや、開発者の独自実装によっては、GETリクエストにBodyを含めるケースが稀に存在します。
今回は、この**「GETリクエストにおけるBodyの有無」**が、CloudFrontでのエラーを引き起こす引き金となっていました。
原因:CloudFrontは「Body付きGETリクエスト」を403で拒否する仕様
WAFや権限周りの設定変更を行っても解決しないため、AWS公式ドキュメント(開発者ガイド)の仕様を再確認しました。その結果、カスタムオリジンへのリクエストとレスポンスの動作に関する項目に、以下の明確な記載を発見しました。
GET requests that include a body If a viewer GET request includes a body, CloudFront returns an HTTP status code 403 (Forbidden) to the viewer.
(日本語訳:ビューワーのGETリクエストに本文(Body)が含まれている場合、CloudFrontはHTTPステータスコード 403 (Forbidden) をビューワーに返します。)
なぜこの仕様なのか?(技術的背景)
HTTPプロトコルの仕様(RFC 7231等)において、GETリクエストへのBody付与は「禁止」こそされていないものの、「意味論的に定義されていない」ため推奨されていません。
CloudFrontのようなCDN(コンテンツデリバリネットワーク)は、リクエストの内容をキーにしてキャッシュを制御します。GETリクエストにBodyが含まれると、キャッシュの整合性が取れなくなったり、セキュリティリスクが生じたりする可能性があるため、AWS側で厳格にブロックしているものと考えられます。
ローカル環境やALB直接アクセスで成功していたのは、それらがCloudFrontほど厳密にHTTPリクエストの構造をチェックせず、Body付きGETを許容していたためです。
解決策と動作確認
原因が特定できたため、リクエスト内容を修正して検証を行いました。 修正方針はシンプルで、**「GETリクエストからBody(--data-raw)を削除する」**ことです。
※本来渡したかったパラメータ(emailなど)は、BodyではなくURLクエリパラメータ(?email=...)として付与するか、HTTPメソッドをPOSTに変更するなどの対応が必要です。
修正後のリクエスト(検証)
Bash
# Body(--data-raw)を削除し、必要に応じてクエリパラメータへ移行
curl --location --request GET 'https://example.com/api/user/env'
結果
上記リクエストを実行したところ、403 Forbiddenエラーは解消され、正常にAPIからのレスポンス(200 OK)が返ってきました。
まとめ:CloudFrontでの403エラー回避は「HTTP標準への準拠」が鍵
今回のトラブルシューティングを通じて、「CloudFrontはHTTPリクエストの構造に対して厳格である」ということが改めて確認できました。
ローカル環境や、柔軟性の高いロードバランサー(ALB)では許容されていた「GETリクエストへのBody付与」も、RFC標準やCDNの仕様に照らし合わせるとブロックの対象となります。
今後の開発に向けたチェックリスト
もし、CloudFront経由で「403 Forbidden」が発生し、WAFや権限設定に問題がない場合は、以下の観点でAPI設計を見直してみてください。
- HTTPメソッドの確認
- データの取得(Read)目的で
GETを使う場合、パラメータは必ず**クエリパラメータ(URLパラメータ)**に含める。 GETリクエストには、決してBody(JSONデータなど)を含めない。
- データの取得(Read)目的で
- データ送信が必要な場合
- 検索条件が複雑すぎる、あるいは個人情報などURLに含めたくないデータを送信したい場合は、
GETではなくPOSTメソッドの使用を検討する(検索用途であっても、Bodyを使うならPOSTが推奨されます)。
- 検索条件が複雑すぎる、あるいは個人情報などURLに含めたくないデータを送信したい場合は、
- 「ローカルで動く」を過信しない
- 開発環境と本番環境(CloudFront経由)では、ネットワーク層での挙動が異なります。早い段階でステージング環境などでの疎通確認を行うことが重要です。
Web標準に準拠したAPI設計を心がけることで、CloudFrontなどのマネージドサービスをトラブルなく活用することができます。同じエラーに遭遇した方の助けになれば幸いです。
