5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APIGatewayとFastAPIのCORS設定のお話

Last updated at Posted at 2025-03-02

はじめに

今回はAWS環境においてAPIGatetayとFastAPIにて、CORS設定でこんなお話がありましたという内容です。

結論としては、API GatewayがHTTP統合でバックエンドと連携をするのであれば、バックバックエンド側(FastAPI)でCORS設定した方が単純で良さそうでしたという話です。

ただ、ここまで至る紆余曲折があったので情報の整理をかねて記事にします。

CORSとは

CORSについては、先人達が分かりやすい記事をたくさん投稿してくださっているので参考記事を引用しながら簡単に説明します。詳細は参考記事を見てください。

CORS(Cross-Origin Resource Sharing)について

CORSの説明の前にオリジン(Origin)について簡単に説明しておくと、URL内の「スキーム+ホスト+ドメイン+ポート番号」までのことをオリジンと言います[1](例:https://www.n-hanshin.com:334 ※1)。このオリジンが異なる場合にアクセス制御が行われ、そのメカニズムがCORSと呼ばれるものになります。

今回の構成のようにフロントエンドとバックエンドのサーバーが異なる場合、オリジンが異なるためCORS対応を行う必要があります。
もし、どのフロントエンド(クライアント)側からでもwebサーバーのリソースが操作可能な場合は不正なデータ操作や漏洩につながることは容易に想像できると思います。なので、異なるオリジンからのアクセスは制限する必要あり、そのための設定がCORSです。
なぜCORSが必要かは参考記事[2][3]を見て頂くと分かりやすいと思います。

プリフライトリクエストとヘッダーについて

CORSによる通信が発生し、アクセスの承認をする必要のあるAPIリクエストの場合、プリフライトリクエストと呼ばれるお伺いが行われた後に、本来のリクエストが行われます。
このプリフライトリクエストはOPTIONSメソッドで行われ、APIに許可されている通信内容をヘッダー情報として取得します。
プリフライトリクエスト以降のやり取りに取得したヘッダーがなければフロントエンド(クライアント)側はレスポンス内容などリソースにアクセスができない状態になります。

ヘッダーの内容としては以下の項目が使用されます。

CORSに関連するHTTPヘッダの例
cors_headers.png
(プリフライトリクエスト(単純ではないリクエスト)より引用[4]

また、通信フローは以下の手順になります。

プリフライトリクエストの場合の通信フロー
① クライアントは https://aaa.com へアクセス。
aaa.com のサーバはレスポンスを返す。
③ ブラウザは https://bbb.com へリクエスト送信して問題ないかを確認。(呼び出し元ドメインや送信予定のリクエストのメソッド・ヘッダ情報を送信)
④ サーバ bbb.com は③の条件のリクエストを受け入れOKの場合、受け入れ可能条件を送信。
⑤ ブラウザは追加コンテンツ取得のため、https://bbb.com へアクセス。
⑥ サーバ bbb.comAccess-Control-Allow-Originヘッダに表示を許可する呼び出し元として https://aaa.com/ を指定し、コンテンツを送信。
preflight_flow.png
(プリフライトリクエスト(単純ではないリクエスト)より引用[4]

今回の状況について

おおまかな構成について

フロントエンドとバックエンドの大まかな構成は以下の図のようになっていました。

  • フロントエンド
    • S3にHTTPなどのコンテンツを配置し、CloudFrontで配信する
  • バックエンド
    • API Gatewayを用いてAPIのエンドポイントを公開し、その裏側はECSでAPI毎の機能を提供する
    • ECSはVPC内に配置されており、API GatewayをVPC統合で連携させている
      • プライベート統合なので、厳密にはNLBが間に設定されている
    • ECSのバックエンドの機能はFastAPIで実装されている

archi.png

API Gatewayの設定内容について

APIの設定の状況

APIGatewayの設定はリソースパスを定義して、バックエンドのAPI毎にルーティングを行っていました。
また、API毎の設定としてVPCプロキシ統合にチェックを入れており、統合リクエストと統合レスポンスをカスタマイズしない状態にしていました。

コンソール画面の状態としては、以下の画像のような状態でした(設定内容は例です)。

APIGateway.png
APIGateway_VPCプロキシ.png

note:
この設定を見て、「あれ?」と思われる方もいると思います。
後の解決策にても述べますが、リソースパスの設定は不要でした。

CORS設定の状況

CORSの設定もAPI Gatewayのコンソール画面で行っていました。
リソースの詳細から「CORSを有効にする」から、許可するメソッドやOriginの設定を行いました。

APIGateway_CORS1.png

APIGateway_CORS2.png

バックエンド(FastAPI)の状況など

バックエンド(FastAPI)
API Gatewayで設定できているという認識のため、こちらは何も設定は施していない状況でした。

フロントエンド
フロントエンド側で必要なCORSによるAPI通信の設定は有効にしていました。

発生した問題

問題発覚時のエラー

さて、ここまで紹介した設定状況でフロントエンド(クライアント)側からAPIを打鍵すると、以下のようなエラーが発生しました。

Access to fetch at 'http://apigateway.AAAA:3000/' from origin 'http://cloudfront.BBBB:8080'has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

http://apigateway.AAAA:3000/はAPIのアドレス、http://cloudfront.BBBB:8080はフロント(クライアント)のアドレスだと解釈してください。

つまり「http://cloudfront.BBBB:8080/からhttp://apigateway.AAAA:3000へのアクセスはCORSポリシーによってブロックされています。」と注意されてしまいました。

状況を整理して分かったこと

このエラーが発生した通信の内容を整理した結果、以下の状態になっていることがわかりました。

  • 異常だと思われること
    • プリフライトリクエスト後のリクエストに対して、レスポンスヘッダー中にAccess-Control-Allow-Originが存在しないこと
  • 正常だと思われること
    • プリフライトリクエストは成功していること(200番台のレスポンス)
    • プリフライトリクエストにはAccess-Control-Allow-OriginなどのCORSに必要なヘッダーが付与されていること
    • CORSに必要なヘッダーの設定値(許可されている項目)は想定しているあたいであること

プリフライトリクエストとヘッダーについてで引用した通信フローに当てはめると、⑥の通信だけが異常な状態でした。

解決策

対応策としては単純で、バックエンド側でAPI通信を制御する機能を持っているのでそちらに一任してしまうことでした。

FastAPIでCORS設定を行う方法としては、CORSMiddlewareを用いてCORSで許可する通信を設定すれば良いだけでした。このミドルウェアはOPTIONSメソッドのリクエストを横取りし、適切なヘッダー情報を付与しレスポンスを返してくれます[5]

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/")
async def main():
    return {"message": "Hello World"}

なので、API Gateway側でリソースパスなどをマメに設定し、CORSを有効にすることが不要になりました。
具体的なAPI Gatewayの設定は「HTTPプロキシ統合」となるように設定すれば問題ありませんでした[6]

問題だったこと

では、なぜAPI GatewayでのCORS設定が上手くいかなかったのか考えてみます。
awsの資料などを読み自ら解釈した点などがあるので、「ココ違うよ」などがあれば教えてください。

結論として「VPCプロキシ統合を有効にしていた状態でCORS設定をしたこと」が諸悪の根源(問題)だと考えています。

理由は以下の2つです。

1. VPCプロキシ統合を有効にすることで、統合レスポンスが編集できなかったため
2. API GatewayでCORS設定を有効にすると、統合レスポンスでヘッダー情報を付与するため

1.については、コンソール画面でも説明されている通りにクライアントのリクエスト、APIサーバーからのレスポンスを編集せずに返すことが説明されています。

また、2.についてはawsのディベロッパーガイドの「API Gateway での REST API の CORS」に以下のような記載[7]があります。

「AWS Management Consoleを使用して非プロキシ統合の CORS を有効にする」
AWS Management Consoleを使用して CORS を有効にすることができます。API Gateway は、OPTIONS メソッドを作成し、Access-Control-Allow-Origin ヘッダーを既存のメソッド統合レスポンスに追加します。
これは常に機能するとは限りません。場合によっては、少なくとも 200 個すべてのレスポンスに対して、すべての CORS 対応メソッドの Access-Control-Allow-Origin ヘッダーを返すように統合レスポンスを手動で変更する必要があります。

加えて、「プロキシ統合の CORS サポートを有効にする[8]」では以下のようにも記載されています。

バックエンドが Access-Control-Allow-Origin ヘッダー、Access-Control-Allow-Methods ヘッダー、Access-Control-Allow-Headers ヘッダーを返す必要があります

以上のことから、プロキシ統合にてCORS設定をする際にはバックエンドでCORSのヘッダーを付与する必要があったが、見落としていたことが問題でした。

おわりに

CORSポリシーによってブロックされたエラーを発端に、CORSについてやAPIGateway、FastAPIの設定方法を調べた内容をまとめてみました。

問題だったことでも言及しましたが、プロキシ統合ではバックエンドでのCORSのためのヘッダー付与が必要でした。しかし、API Gatewayに中途半端にCORSの責務を負わせるなら、バックエンド側でCORS設定した方が単純で良さそうでしたという話でした。


参考記事

注釈

※1: なんでや阪神関係ないやろ

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?