FastAPI CORS 設定と使い方を分かりやすく解説

FastAPI CORS 設定と使い方を分かりやすく解説

はじめに

現代のウェブアプリケーション開発において、APIサーバーとクライアント(ブラウザ上のフロントエンド)はしばしば異なるオリジン(ドメイン、ポート、プロトコル)で動作します。例えば、APIが api.example.com で、フロントエンドが app.example.comlocalhost:3000 で動作している場合などです。このような異なるオリジン間でのHTTPリクエストは、ブラウザに実装されているセキュリティメカニズムである同一オリジンポリシー (Same-Origin Policy: SOP) によって制限されます。

同一オリジンポリシーは、悪意のあるウェブサイトがユーザーの同意なく他のサイトのデータにアクセスすることを防ぐための重要なセキュリティ機能です。しかし、正当な理由で異なるオリジン間でのリソース共有が必要な場合、このポリシーが妨げとなります。ここで登場するのが、CORS (Cross-Origin Resource Sharing) という仕組みです。CORSは、サーバーが自身のリソースへのオリジン間アクセスを明示的に許可するための標準的な方法を提供します。

FastAPIは、Pythonでモダン、高速、学習コストの低いAPIを構築するためのフレームワークとして人気があります。その設計思想から、様々な環境からのアクセスを想定しており、CORSの設定も非常に簡単に行えるようになっています。

この記事では、FastAPIにおけるCORS設定について、その基礎から応用、そしてセキュリティ上の注意点までを網羅的に解説します。CORSの基本的な仕組みから始め、FastAPIのCORSMiddlewareを使った具体的な設定方法、様々なユースケースに応じた設定例、そしてトラブルシューティングの方法までを詳細に説明します。この記事を読めば、あなたのFastAPIアプリケーションに安全かつ適切にCORSを設定できるようになるでしょう。

CORSの基礎知識

FastAPIでの設定に入る前に、CORSがなぜ必要なのか、そしてどのように機能するのかを理解しておくことが重要です。

オリジン(Origin)とは?

まず、「オリジン」とは何かを明確にしましょう。ウェブにおけるオリジンは、以下の3つの要素の組み合わせで定義されます。

  1. スキーム (Scheme): 通信プロトコル(例: http, https
  2. ホスト (Host): ドメイン名またはIPアドレス(例: example.com, localhost, 192.168.1.1
  3. ポート (Port): サーバーがリクエストを待ち受けるポート番号(例: 80, 443, 3000

これら3つの要素が全て一致する場合にのみ、同じオリジンと見なされます。例えば、以下のURLは全て異なるオリジンです。

  • http://example.com (スキームが異なる)
  • https://example.com:8080 (スキームとポートが異なる)
  • https://api.example.com (ホストが異なる)
  • https://example.com (上記とはポートが異なる – HTTPSのデフォルトは443)

同一オリジンポリシー (Same-Origin Policy: SOP)

ブラウザは、JavaScriptなどのスクリプトが、そのスクリプトをダウンロードしたオリジンとは異なるオリジンに対して特定の種類のHTTPリクエスト(特にレスポンスを読み取るリクエスト)を行うことを制限しています。これが同一オリジンポリシーです。

例えば、https://malicious.com のウェブサイトで実行されているJavaScriptが、ユーザーがログインしている https://bank.com のAPIエンドポイントに勝手にリクエストを送信し、そのレスポンスデータを読み取ろうとするシナリオを考えてみましょう。もしSOPがなければ、悪意のあるサイトはユーザーの認証情報(クッキーなど)を使って銀行の情報を盗み出すことができてしまいます。SOPはこれを防ぎます。ブラウザは https://malicious.com から https://bank.com へのリクエストを送信しますが、レスポンスが返ってきてもJavaScriptがその内容を読み取ることをブロックします。

CORSが解決する問題

同一オリジンポリシーはセキュリティ上重要ですが、現代のウェブアプリケーションでは異なるオリジン間でのリソース共有が必要な場面が多々あります。例えば、

  • https://frontend.com で動作するシングルページアプリケーション (SPA) が https://api.com で動作するAPIからデータを取得する。
  • https://my-service.com が、別のサービス https://third-party-api.com の公開APIを利用する。
  • 開発中に http://localhost:3000 で実行中のフロントエンドが、http://localhost:8000 で実行中のバックエンドAPIにアクセスする。

CORSは、これらの正当なオリジン間リクエストを可能にするためのメカニズムです。サーバー側が、どのオリジンからのリクエストを受け付けるかを明示的に指定することで、ブラウザはその指定に基づいてアクセス制御を行います。

CORSの仕組み

CORSは、HTTPヘッダーを利用してサーバーとブラウザ間で通信を行います。リクエストの種類によって、ブラウザの動作は異なります。

シンプルリクエスト (Simple Requests)

一部のHTTPリクエストは「シンプルリクエスト」と呼ばれ、ブラウザはSOP違反の可能性があっても、まずサーバーに直接リクエストを送信します。サーバーが適切なCORS関連のレスポンスヘッダーを返せば、ブラウザはレスポンスをJavaScriptに渡します。そうでない場合、ブラウザはレスポンスをブロックします。

シンプルリクエストの条件は以下の通りです。

  • メソッド: GET, HEAD, POST のいずれか
  • ヘッダー: CORSセーフリストヘッダー(自動的にブラウザが設定するものや、一般的なもの)のみを使用。具体的には、以下のヘッダーとその値のみが許可されます(値の制限もあります)。
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (ただし、値は application/x-www-form-urlencoded, multipart/form-data, text/plain のいずれかに限る)
    • Range (シンプルなバイト範囲リクエストのみ)
  • XMLHttpRequest.upload にイベントリスナーが登録されていないこと。
  • リクエストに ReadableStream オブジェクトが使用されていないこと。

これらの条件を一つでも満たさない場合は、シンプルリクエストとは見なされず、次に説明するプリフライトリクエストが必要になります。

プリフライトリクエスト (Preflight Requests)

シンプルリクエストの条件を満たさない、より複雑なリクエスト(例: PUT, DELETE メソッド、カスタムヘッダーの使用、Content-Typeapplication/json のPOSTリクエストなど)の場合、ブラウザは実際のリクエストを送信する前に、プリフライトリクエストと呼ばれるOPTIONSリクエストをサーバーに送信します。

このOPTIONSリクエストは、実際のリクエストで使われるメソッド、ヘッダー、オリジンなどをサーバーに伝え、「この実際のリクエストを送信しても大丈夫ですか?」と問い合わせるためのものです。

サーバーはプリフライトリクエストに対して、以下のようなCORS関連のレスポンスヘッダーを返します。

  • Access-Control-Allow-Origin: どのオリジンからのリクエストを許可するかを指定します。特定オリジン (https://frontend.com)、または全てのオリジン (*) を指定できます。
  • Access-Control-Allow-Methods: 実際のリクエストで使用を許可するHTTPメソッドを指定します(例: GET, POST, PUT, DELETE)。
  • Access-Control-Allow-Headers: 実際のリクエストで使用を許可するカスタムヘッダーを指定します(例: X-Requested-With, Content-Type, Authorization)。
  • Access-Control-Allow-Credentials: クッキーや認証ヘッダー(Authorizationヘッダーなど)を含むリクエストを許可するかどうかを指定します (true または省略)。注意: Access-Control-Allow-Origin'*' を指定した場合、このヘッダーは true にできません。
  • Access-Control-Max-Age: プリフライトリクエストの結果をブラウザがキャッシュできる時間を秒単位で指定します。この時間が経過するまでは、同じリクエストに対して再度プリフライトリクエストは送信されません。

ブラウザは、サーバーからのプリフライトレスポンスを解析し、実際のリクエストが許可されているかを確認します。

  • 許可されていれば、ブラウザは続けて実際のHTTPリクエストを送信します。
  • 許可されていない場合(例えば、Access-Control-Allow-Origin にリクエスト元のオリジンが含まれていない、許可されていないメソッドやヘッダーが使われているなど)、ブラウザは実際のリクエストを送信せず、コンソールにCORSエラーを表示します。

このように、CORSはサーバーとブラウザが協調してオリジン間アクセスを制御する仕組みです。サーバー側でCORSを適切に設定することが、クライアントからのオリジン間リクエストを受け付けるために不可欠となります。

FastAPIでのCORS設定方法

FastAPIでは、CORS設定を非常に簡単に行うためのミドルウェアが提供されています。fastapi.middleware.cors.CORSMiddleware を使うことで、アプリケーション全体または特定のパスに対して柔軟なCORSポリシーを適用できます。

ミドルウェアとは?

FastAPIにおけるミドルウェアは、リクエストがルーティングされる前や、レスポンスがクライアントに返される前に、特定の処理を実行するための機能です。複数のミドルウェアをスタック(層)のように重ねて使用でき、リクエストとレスポンスの処理フローをカスタマイズできます。

CORSMiddleware は、まさにこのミドルウェアの仕組みを利用して、すべての(または指定された)受信リクエストに対してCORS関連のヘッダーをチェックし、適切なCORSレスポンスヘッダーを付与する役割を担います。

CORSMiddleware の基本的な使い方

CORSMiddleware を使用するには、まずFastAPIアプリケーションインスタンスにミドルウェアとして追加します。通常はアプリケーションの初期化時に行います。

“`python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

アプリケーションにCORSMiddlewareを追加

app.add_middleware(
CORSMiddleware,
allow_origins=[““], # 後述しますが、本番環境では’‘は非推奨です
allow_credentials=True,
allow_methods=[““],
allow_headers=[“
“],
)

@app.get(“/”)
async def read_root():
return {“Hello”: “World”}

@app.post(“/items/”)
async def create_item(item: dict):
return item
“`

この例では、アプリケーションインスタンス app に対して add_middleware メソッドを使って CORSMiddleware を追加しています。CORSMiddleware をインスタンス化する際に、CORSの許可ポリシーを定義する様々なパラメータを指定します。上記の例では、全てのオリジン (*) からの、全てのメソッド (*)、全てのヘッダー (*) を許可し、認証情報 (allow_credentials=True) も許可するという、非常に緩い設定になっています。この設定は開発環境では便利ですが、セキュリティリスクがあるため本番環境では絶対に使用しないでください。

CORSMiddleware の主要なパラメータ詳細

CORSMiddleware のインスタンス化時に指定できる主なパラメータは以下の通りです。これらのパラメータを使って、CORSポリシーを細かく制御します。

  1. allow_origins:

    • 型: List[str]
    • 説明: リクエストを許可するオリジンのリストを指定します。オリジンは スキーム://ホスト[:ポート] の形式で指定します。
    • 例:
      • 特定の単一オリジンを許可: ["https://frontend.com"]
      • 複数のオリジンを許可: ["https://frontend.com", "https://another-frontend.com"]
      • 全てのオリジンを許可: ["*"] これは非常に緩い設定で、セキュリティリスクが高いため、本番環境では避け、代わりに許可するオリジンを明示的にリストアップするか、allow_origin_regex を使用することを強く推奨します。
      • 注意: allow_origins=['*']allow_credentials=True は同時に指定できません。全てのオリジンからの認証情報付きリクエストを許可することは、セキュリティ上の懸念があるためブラウザによってブロックされます。認証情報付きリクエストを許可する場合は、allow_origins に具体的なオリジンをリストアップする必要があります。
  2. allow_origin_regex:

    • 型: str
    • 説明: リクエストを許可するオリジンを正規表現で指定します。allow_origins よりも柔軟なマッチングが可能です。
    • 例:
      • 特定のドメインとその全てのサブドメインを許可: r"https://.*\.example\.com"
      • localhost とその任意のポートを許可(開発時など): r"http://localhost:\d+"
    • allow_originsallow_origin_regex の両方を指定した場合、どちらかにマッチすれば許可されます。
  3. allow_credentials:

    • 型: bool
    • 説明: オリジン間リクエストでクッキー、Authorizationヘッダー、TLSクライアント証明書などの認証情報を許可するかどうかを指定します。デフォルトは False です。
    • True に設定すると、サーバーは Access-Control-Allow-Credentials: true ヘッダーをレスポンスに含めます。
    • 重要: allow_credentials=True の場合、allow_origins'*' を指定することはできません。必ず許可する具体的なオリジンをリストアップするか、allow_origin_regex を使用する必要があります。
  4. allow_methods:

    • 型: List[str]
    • 説明: 許可するHTTPメソッドのリストを指定します。デフォルトは ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] です。
    • 例:
      • 一般的なRESTfulメソッドを許可: ["GET", "POST", "PUT", "DELETE"]
      • 全てのメソッドを許可: ["*"] (デフォルトと同じ効果)
    • プリフライトリクエストに対する Access-Control-Allow-Methods ヘッダーに影響します。
  5. allow_headers:

    • 型: List[str]
    • 説明: 許可するリクエストヘッダーのリストを指定します。ブラウザはカスタムヘッダーを含むオリジン間リクエストを送信する前にプリフライトリクエストを行います。サーバーは許可されたヘッダーを Access-Control-Allow-Headers ヘッダーで応答します。
    • デフォルトは [] (何も許可しない) ですが、内部的にはCORSセーフリストヘッダー (Accept, Accept-Language, Content-Language, Content-Type) およびいくつかのブラウザ固有のヘッダーは常に許可されます。
    • 例:
      • 一般的なカスタムヘッダーを許可: ["Content-Type", "Authorization", "X-Requested-With"]
      • 全てのカスタムヘッダーを許可: ["*"] (これはセキュリティリスクとなりうるため注意が必要です。必要なヘッダーのみを許可するのがベストプラクティスです。)
    • プリフライトリクエストに対する Access-Control-Allow-Headers ヘッダーに影響します。
  6. expose_headers:

    • 型: List[str]
    • 説明: ブラウザのJavaScriptからアクセス(読み取り)を許可するレスポンスヘッダーのリストを指定します。デフォルトは [] です。
    • セキュリティ上の理由から、多くのレスポンスヘッダー(例えば Authorization, Server, Set-Cookie など)はデフォルトでJavaScriptから隠蔽されています。これらのヘッダーをフロントエンドから読み取る必要がある場合にここで指定します。
    • 例: ["X-Total-Count", "Link"]
    • 実際のレスポンスに対する Access-Control-Expose-Headers ヘッダーに影響します。
  7. max_age:

    • 型: int
    • 説明: プリフライトリクエストの結果をブラウザがキャッシュする最大時間(秒単位)を指定します。デフォルトは 600 秒(10分)です。
    • 例: max_age=3600 (1時間キャッシュ)
    • キャッシュ時間を長く設定すると、同じオリジン/メソッド/ヘッダーの組み合わせでの後続のリクエストに対してプリフライトリクエストが省略されるため、パフォーマンスが向上します。ただし、CORS設定を変更した場合、古いキャッシュが残っていると問題が発生する可能性があるため注意が必要です。

具体的な設定例とベストプラクティス

ここでは、様々なシナリオに応じた CORSMiddleware の具体的な設定例と、セキュリティを考慮したベストプラクティスについて説明します。

例1: 開発環境での簡単な設定

開発中、フロントエンドが http://localhost:3000 で、バックエンドが http://localhost:8000 で動作しているような場合、一時的に全てのオリジンからのアクセスを許可すると便利です。ただし、これは本番環境では絶対に避けるべき設定です。

“`python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

開発環境向け: 全てのオリジンを許可 (非推奨 for Production)

app.add_middleware(
CORSMiddleware,
allow_origins=[““], # DEVELOPMENT ONLY: 全てのオリジンを許可
allow_credentials=True, # クッキー等も許可するならTrueに
allow_methods=[“
“], # DEVELOPMENT ONLY: 全てのメソッドを許可
allow_headers=[“*”], # DEVELOPMENT ONLY: 全てのヘッダーを許可
)

@app.get(“/”)
async def read_root():
return {“Hello”: “World”}

@app.post(“/data”)
async def post_data(data: dict):
return {“received”: data}

実行コマンド例: uvicorn main:app –reload –port 8000

“`

この設定は開発中の利便性を優先したものですが、本番環境にデプロイすると誰でもあなたのAPIにオリジン間リクエストを送信できるようになり、セキュリティ上の大きな問題を引き起こす可能性があります。

例2: 本番環境での安全な設定(特定のオリジンのみ許可)

本番環境では、アプリケーションのフロントエンドが動作するオリジンや、信頼できる特定のオリジンからのアクセスのみを許可するように厳密に設定する必要があります。

“`python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

本番環境向け: 特定のオリジンのみを許可

origins = [
“https://frontend.com”, # 本番フロントエンドのオリジン
“https://dashboard.frontend.com”, # ダッシュボードなど、別のサブドメイン
“http://localhost:3000”, # 開発中のフロントエンドのオリジン (一時的に含める場合も)
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # クッキーや認証ヘッダーを許可する場合
allow_methods=[“GET”, “POST”, “PUT”, “DELETE”, “PATCH”, “OPTIONS”], # 必要なメソッドのみ許可
allow_headers=[“Authorization”, “Content-Type”], # 必要なヘッダーのみ許可
)

@app.get(“/”)
async def read_root():
return {“Hello”: “World”}

クライアントから認証情報(例: クッキー、Authorizationヘッダー)を送信する場合

@app.get(“/protected_data”)
async def read_protected_data(authorization: str = Header(None)):
if authorization is None:
# 認証ヘッダーがない場合の処理(例: エラーレスポンス)
return {“error”: “Authorization header missing”}, 401
# 認証ヘッダーを使った認証・認可処理
return {“data”: “sensitive data”, “auth_header”: authorization}
“`

この設定例では、allow_origins に許可するオリジンを具体的にリストアップしています。allow_credentials=True と組み合わせることで、これらのオリジンからのリクエストに対してクッキーやAuthorizationヘッダーの使用が許可されます。allow_methodsallow_headers も、アプリケーションで実際に使用する最小限のリストに絞るのがセキュリティ上望ましいです。

例3: サブドメインを持つオリジンを許可する場合 (allow_origin_regex の活用)

example.com のサブドメインとして app.example.com, api.example.com, blog.example.com などがあり、これら全ての *.example.com からのアクセスを許可したい場合、allow_origin_regex が便利です。

“`python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

特定のドメインとその全てのサブドメインを許可

app.add_middleware(
CORSMiddleware,
# HTTPSを使用し、example.com またはその任意のサブドメインからのアクセスを許可
allow_origin_regex=r”https://.*.example.com”,
allow_credentials=True,
allow_methods=[“GET”, “POST”], # 例としてGETとPOSTのみ許可
allow_headers=[“Content-Type”], # 例としてContent-Typeのみ許可
)

@app.get(“/data”)
async def get_data():
return {“message”: “Data from regex allowed origin”}
“`

正規表現 r"https://.*\.example\.com" は、「https:// で始まり、任意の文字列 (.*) の後に .example.com が続く」というパターンにマッチします。これにより、https://example.com 自体と、https://app.example.com, https://sub.domain.example.com などのサブドメインからのHTTPSリクエストが許可されます。正規表現を使う際は、意図しないオリジンまで許可してしまわないように注意深く記述する必要があります。

例4: allow_credentials=Trueallow_origins=['*'] の併用不可について再確認

繰り返しになりますが、allow_credentials=Trueallow_origins=['*'] は同時に設定できません。

“`python

これは正しく動作しない(ブラウザによってブロックされる)

app.add_middleware(
CORSMiddleware,
allow_origins=[““], # 全てのオリジンを許可
allow_credentials=True, # 認証情報も許可しようとする
allow_methods=[“
“],
allow_headers=[“*”],
)
“`

ブラウザは、Access-Control-Allow-Origin: * ヘッダーが返ってきた場合、認証情報(クッキー、Authorizationヘッダーなど)を含むオリジン間リクエストのレスポンスをJavaScriptに渡しません。これは、悪意のあるサイトがユーザーの認証情報を使って任意のサイトから情報を盗み出すリスクを防ぐためです。

もし認証情報を含むリクエストを許可したい場合は、必ず allow_origins または allow_origin_regex で許可するオリジンを具体的に指定する必要があります。

“`python

認証情報を含むリクエストを許可する場合の正しい設定例

app.add_middleware(
CORSMiddleware,
allow_origins=[“https://frontend.com”], # 許可する具体的なオリジンを指定
allow_credentials=True, # 認証情報も許可
allow_methods=[“GET”, “POST”],
allow_headers=[“Authorization”, “Content-Type”],
)
“`

この設定であれば、https://frontend.com からの認証情報を含むリクエストに対して、FastAPIサーバーは Access-Control-Allow-Origin: https://frontend.comAccess-Control-Allow-Credentials: true というヘッダーを含むレスポンスを返せるため、ブラウザはレスポンスを許可します。

セキュリティ上の注意点:allow_origins=['*'] の危険性

開発中に allow_origins=['*'] を使うのは便利ですが、これを本番環境にデプロイすると、以下のようなリスクが発生します。

  1. データ漏洩: SOPによって保護されていたはずの、ユーザーの認証情報(ログインクッキーなど)に依存するAPIエンドポイントに、悪意のあるウェブサイトから勝手にリクエストを送信され、そのレスポンスデータが読み取られてしまう可能性があります。特に、ユーザーが既にログインしている状態で悪意のあるサイトを訪問した場合に問題となります。
  2. CSRFとの組み合わせ: CSRF (Cross-Site Request Forgery) 対策が不十分なエンドポイントに対して、任意のサイトからオリジン間リクエストを送信し、ユーザーの意図しない操作(例: パスワード変更、送金など)を実行させられる可能性があります。CORS自体はCSRFを完全に防ぐものではありませんが、allow_origins=['*'] は攻撃の敷居を下げてしまいます。

したがって、本番環境では必ず allow_origins または allow_origin_regex を使用して、信頼できるオリジンのみからのアクセスを許可するように設定してください。

特定のエンドポイントのみCORSを適用する方法(非推奨だが知っておくべきこと)

一般的に、CORSはアプリケーション全体またはサブパスに対してミドルウェアで設定するのが最も効率的で推奨される方法です。しかし、特定の少数エンドポイントにだけ異なるCORSポリシーを適用したい、あるいはミドルウェアを使わずに直接ヘッダーを制御したいという特殊なケースもあるかもしれません。

このような場合は、レスポンスを返す際に直接CORS関連のヘッダーを付与することで実現できます。FastAPIでは JSONResponseResponse オブジェクトを使ってヘッダーを操作できます。

“`python
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get(“/public_data”)
async def get_public_data():
# このエンドポイントはCORSミドルウェアによる制御を受ける(もしミドルウェアが有効なら)
return {“message”: “This is public data”}

@app.get(“/special_data”)
async def get_special_data():
# このエンドポイントに手動でCORSヘッダーを付与
content = {“message”: “This is special data for one origin”}
headers = {
“Access-Control-Allow-Origin”: “https://special-frontend.com”,
“Access-Control-Allow-Methods”: “GET”,
“Access-Control-Allow-Headers”: “Content-Type”,
“Access-Control-Allow-Credentials”: “true” # 必要なら
}
return JSONResponse(content=content, headers=headers)

@app.options(“/special_data”)
async def options_special_data(response: Response):
# プリフライトリクエスト (OPTIONS) にも手動で応答する必要がある
response.headers[“Access-Control-Allow-Origin”] = “https://special-frontend.com”
response.headers[“Access-Control-Allow-Methods”] = “GET” # 実際のリクエストで許可するメソッド
response.headers[“Access-Control-Allow-Headers”] = “Content-Type” # 実際のリクエストで許可するヘッダー
response.headers[“Access-Control-Allow-Credentials”] = “true” # 実際のリクエストで許可する場合
response.headers[“Access-Control-Max-Age”] = “600” # キャッシュ時間 (秒)
return Response(status_code=204) # OPTIONSリクエストは通常204 No Contentで応答
“`

この方法の欠点は、

  1. 各エンドポイント(GET, POSTなど)に加えて、対応する OPTIONS メソッドのエンドポイントも手動で定義し、CORSヘッダーを正確に設定する必要があること。
  2. ヘッダーの設定ミスや漏れが発生しやすく、管理が煩雑になること。
  3. ミドルウェアによる自動的なプリフライトリクエストの処理やヘッダー付与の恩恵を受けられないこと。

特殊な要件がない限り、CORSMiddleware を使用してアプリケーション全体またはパスプレフィックスに対してCORSを設定することを強く推奨します。 ミドルウェアはOPTIONSリクエストの処理も自動で行ってくれます。

CORS設定のトラブルシューティング

CORS関連のエラーは、ウェブ開発でよく遭遇する問題の一つです。エラーメッセージがブラウザのコンソールに表示され、サーバー側ではCORS関連のエラーログが出ないことが多いため、原因の特定が少し難しい場合があります。

よくあるエラーメッセージとその原因、そしてデバッグ方法について説明します。

よくあるエラーメッセージ

  1. Access to fetch at '<API_URL>' from origin '<FRONTEND_ORIGIN>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    • 原因:
      • リクエスト元のオリジン (<FRONTEND_ORIGIN>) が、サーバーのCORS設定 (allow_origins, allow_origin_regex) で許可されていない。
      • サーバー側にCORSミドルウェアが全く設定されていない。
      • CORSミドルウェアは設定されているが、特定のエンドポイントに対してはミドルウェアが適用されていないか、手動設定が間違っている。
      • プリフライトリクエスト (OPTIONS) が失敗している(次に説明)。
    • 解決策:
      • FastAPIアプリケーションに CORSMiddleware が正しく追加されているか確認します。
      • allow_origins または allow_origin_regex に、リクエスト元のオリジンが正しく含まれているか確認します。ポート番号も含めて完全に一致している必要があります (http://localhost:3000http://localhost:8000 は異なるオリジンです)。
      • 本番環境では allow_origins=['*'] ではなく、具体的なオリジンを指定しているか確認します。
      • サーバーログを確認し、OPTIONSリクエストや実際のリクエストがFastAPIアプリケーションに到達しているか、エラーが発生していないか確認します。
  2. Access to fetch at '<API_URL>' from origin '<FRONTEND_ORIGIN>' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

    • 原因:
      • ブラウザが送信したプリフライトリクエスト (OPTIONSメソッド) に対して、サーバーが2xx系の成功ステータスコード以外を返している(例: 404 Not Found, 500 Internal Server Errorなど)。
      • FastAPIアプリケーションで、OPTIONSメソッドに対するルーティングが正しく設定されていないか、エラーが発生している。
      • ミドルウェアの設定で OPTIONS メソッドが allow_methods に含まれていない(デフォルトでは含まれますが、もし明示的にリストアップしている場合は確認)。
    • 解決策:
      • ブラウザの開発者ツール(Networkタブ)で、プリフライトリクエスト(OPTIONSメソッドのリクエスト)の詳細を確認します。リクエストURL、メソッド、ヘッダーが正しいか、そして特にレスポンスのステータスコードが204または200になっているか確認します。
      • サーバーログを確認し、OPTIONSリクエストがFastAPIアプリケーションに到達しているか、処理中にエラーが発生していないか確認します。
      • CORSMiddleware を使用している場合、OPTIONSメソッドはデフォルトで許可されます。もし allow_methods を明示的に指定している場合は、そこに 'OPTIONS' が含まれているか確認します。
  3. Access to fetch at '<API_URL>' from origin '<FRONTEND_ORIGIN>' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

    • 原因:
      • クライアント(ブラウザ)が認証情報(クッキー、Authorizationヘッダーなど)を含めてリクエストを送信しているにも関わらず、サーバーが Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true の両方をレスポンスヘッダーに含めている。ブラウザはこの組み合わせをセキュリティ上の理由でブロックします。
    • 解決策:
      • CORSMiddleware の設定を確認します。allow_credentials=True としている場合、allow_origins'*' を指定することはできません。許可するオリジンを具体的にリストアップするか、allow_origin_regex を使用する必要があります。
      • クライアント側で認証情報を含める設定(例: fetch APIの credentials: 'include', XMLHttpRequestwithCredentials = true)を行っているか確認し、意図した設定になっているか見直します。
  4. Access to fetch at '<API_URL>' from origin '<FRONTEND_ORIGIN>' has been blocked by CORS policy: Request header field <CUSTOM_HEADER> is not allowed by Access-Control-Allow-Headers in preflight response.

    • 原因:
      • クライアントがカスタムヘッダー(例: Authorization, X-Custom-Header など)を含むリクエストを送信しており、ブラウザがプリフライトリクエストを行っているが、サーバーからのプリフライトレスポンスの Access-Control-Allow-Headers ヘッダーに、クライアントが使用しようとしているカスタムヘッダーが含まれていない。
    • 解決策:
      • CORSMiddlewareallow_headers パラメータに、クライアントが送信しようとしているカスタムヘッダー名が正しく含まれているか確認します。ヘッダー名は大文字・小文字を区別しないのが一般的ですが、リストに指定する際は慣例的なキャピタライズ(例: Authorization)で記述するのが良いでしょう。

デバッグ方法

CORSの問題をデバッグする際には、以下のツールと手順が役立ちます。

  1. ブラウザの開発者ツール (Developer Tools)

    • Consoleタブ: CORSエラーメッセージが最も詳細に表示される場所です。エラーメッセージをよく読み、どのヘッダーやオリジンが問題になっているかを特定します。
    • Networkタブ: 実際のリクエストとレスポンスの詳細を確認できます。問題のリクエストを選択し、Headersタブを開きます。
      • Request Headers: クライアントが送信したオリジン (Origin ヘッダー) や、使用したメソッド、カスタムヘッダー (Access-Control-Request-Method, Access-Control-Request-Headers – これらはプリフライトリクエストの場合) を確認します。
      • Response Headers: サーバーから返されたCORS関連ヘッダー (Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Max-Age, Access-Control-Expose-Headers) を確認します。これらのヘッダーの値が、リクエスト元のオリジンや使用したメソッド/ヘッダーを許可しているかチェックします。特にプリフライトリクエスト (OPTIONS) のレスポンスヘッダーが重要です。
      • Status Code: レスポンスのHTTPステータスコードを確認します。特にプリフライトリクエストが成功しているか(204または200)を確認します。
  2. サーバーログ

    • FastAPIアプリケーションの実行中に、受信したリクエスト(特にOPTIONSリクエスト)や、FastAPI内部で発生したエラーログを確認します。FastAPIがリクエストを受け取っているか、ミドルウェアが実行されているかなどを確認するのに役立ちます。
  3. CURLなどのコマンドラインツール

    • ブラウザの挙動に依存せず、APIエンドポイントがCORSヘッダーを正しく返しているかを確認するために、curl コマンドを使うのも有効です。
    • 例: 特定オリジンからのGETリクエストをシミュレート

      “`bash
      curl -I -H “Origin: https://frontend.com”

      -I はヘッダーのみ表示

      -H “Origin: …” でリクエスト元のオリジンを指定

      “`
      * 例: 特定オリジンからのPUTリクエストに対するプリフライトリクエストをシミュレート

      “`bash
      curl -I -X OPTIONS -H “Origin: https://frontend.com” -H “Access-Control-Request-Method: PUT” -H “Access-Control-Request-Headers: Content-Type, Authorization”

      -X OPTIONS でOPTIONSメソッドを指定

      Access-Control-Request-… ヘッダーで実際のリクエスト情報をサーバーに伝える

      “`
      * これらのコマンドの出力(レスポンスヘッダー)を見て、期待するCORSヘッダーが含まれているか確認します。

デバッグの際は、「ブラウザが何をサーバーに問い合わせていて(プリフライトリクエスト)、サーバーがそれにどう答えているか(プリフライトレスポンス)、そして実際のリクエストに対してサーバーがどう答えているか(実際のリクエストのレスポンス)」 というCORSの基本的な流れを意識しながら、ブラウザの開発者ツールやサーバーログを確認することが重要です。

高度なトピック

複数のミドルウェアの順序

FastAPIアプリケーションに複数のミドルウェアを追加する場合、追加した順序が重要になります。add_middleware で最初に追加されたミドルウェアが最も外側(リクエスト処理チェーンの最初とレスポンス処理チェーンの最後)で実行され、最後に追加されたミドルウェアが最も内側(ルーティングの近く)で実行されます。

“`python
app = FastAPI()

1. CORSMiddleware (最も外側)

app.add_middleware(
CORSMiddleware,
allow_origins=[“https://frontend.com”],
allow_credentials=True,
allow_methods=[““],
allow_headers=[“
“],
)

2. GZipMiddleware (CORSの内側)

from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)

3. Authentication/Authorization Middleware (最も内側、ルーティングの直前/直後)

例: app.add_middleware(AuthMiddleware, …)

… ルーティング …

“`

通常、CORSMiddleware は他のミドルウェアよりも外側(つまり、add_middleware の比較的早い段階)に追加することが推奨されます。これは、CORSチェックがリクエストの早い段階で行われるべきであり、CORSポリシー違反のリクエストに対しては後続のミドルウェア(認証、ロギング、圧縮など)やアプリケーションコードの処理をスキップできるためです。もし認証ミドルウェアよりも内側にCORSミドルウェアを置くと、未認証のオリジン間リクエストでも認証チェックが実行されてしまう可能性があります(CORSミドルウェアがリクエストをブロックする前に)。

CORS以外のセキュリティ対策との組み合わせ

CORSはオリジン間リソース共有を安全に行うためのメカニズムであり、ウェブアプリケーションのセキュリティ対策全体の一部に過ぎません。CORSを設定したからといって、他のセキュリティ対策が不要になるわけではありません。以下のような対策も組み合わせて講じることが重要です。

  • HTTPSの使用: 通信経路を暗号化し、中間者攻撃を防ぎます。本番環境では必須です。
  • 認証と認可: APIエンドポイントへのアクセスを、認証されたユーザーに限定し、さらにそのユーザーが要求された操作を行う権限を持っているかを確認します。Bearer Token, API Key, OAuth2などの仕組みをFastAPIで実装します。
  • 入力値の検証: クライアントから送信されたデータを常に検証し、不正なデータや悪意のあるコードの実行を防ぎます(例: XSS, SQLインジェクション対策)。FastAPIではPydanticを使った入力検証が容易です。
  • CSRF対策: ユーザーの認証情報に依存する操作(POST, PUT, DELETEなど)に対して、オリジン間リクエストではないことを確認するメカニズム(例: CSRFトークン)を導入します。FastAPIにはCSRF保護のためのライブラリ(例: fastapi-csrf-protect)も存在します。allow_credentials=True を設定する場合は、CSRF対策の重要性が特に高まります。
  • CSP (Content Security Policy): ブラウザに対して、ロードを許可するリソース(スクリプト、スタイルシート、画像など)のオリジンを指定することで、XSS攻撃などのリスクを軽減します。これはAPIサーバー側ではなく、通常はフロントエンドを提供するウェブサーバーや、CDNなどで設定します。
  • レート制限: 短時間での過剰なリクエストを防ぎ、DoS攻撃などからサーバーを保護します。
  • セキュリティヘッダー: HSTS (HTTP Strict Transport Security), X-Content-Type-Options, X-Frame-Optionsなどのセキュリティ関連HTTPヘッダーを適切に設定します。FastAPIのミドルウェアやスターレットの機能を使ってこれらを設定できます。

CORSは、これらのセキュリティ対策と組み合わせて使用することで、より堅牢なウェブアプリケーションを構築できます。

まとめ

この記事では、FastAPIにおけるCORSの設定と使い方について、CORSの基礎知識から始めて、CORSMiddleware の詳細なパラメータ解説、具体的な設定例、そしてトラブルシューティングの方法までを網羅的に解説しました。

CORSは、現代のウェブアプリケーション開発において、異なるオリジン間で安全にリソースを共有するために不可欠な仕組みです。FastAPIは fastapi.middleware.cors.CORSMiddleware を提供することで、このCORS設定を非常に簡単かつ柔軟に行えるようにしています。

重要なポイントをまとめると以下のようになります。

  • CORSはブラウザの同一オリジンポリシーによる制限を、サーバー側の許可に基づいて緩和するメカマニズムである。
  • プリフライトリクエスト(OPTIONS)と実際のリクエストという二段階で通信が行われる場合がある。
  • FastAPIでは CORSMiddleware を使うのが最も推奨されるCORS設定方法である。
  • CORSMiddleware の主要パラメータ (allow_origins, allow_origin_regex, allow_credentials, allow_methods, allow_headers など) を適切に設定することで、詳細なCORSポリシーを定義できる。
  • セキュリティ上の理由から、本番環境では allow_origins=['*'] の使用は避け、信頼できる特定のオリジンのみを許可するように厳密に設定すべきである。特に allow_credentials=True を使用する場合は、許可するオリジンを具体的に指定する必要がある。
  • CORS設定に問題がある場合は、ブラウザの開発者ツール(ConsoleとNetworkタブ)やサーバーログ、curl コマンドなどを活用して原因を特定し、CORSMiddleware の設定を見直す。
  • CORSはセキュリティ対策の一部であり、HTTPS、認証・認可、入力値検証、CSRF対策など、他のセキュリティ対策と組み合わせて講じる必要がある。

FastAPIの CORSMiddleware を適切に設定することで、安全かつ効率的に異なるオリジンからのAPIアクセスを処理できるようになります。本記事が、あなたのFastAPIアプリケーション開発におけるCORS設定の理解と実践に役立つことを願っています。安全なAPI開発を進めていきましょう。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール