FastAPI CORS 完全ガイド:設定からエラー対応まで

はい、承知いたしました。FastAPIにおけるCORS設定について、設定方法からエラー対応までを網羅した約5000語の詳細な解説記事を作成します。


FastAPI CORS 完全ガイド:設定からエラー対応まで

はじめに:なぜCORSが必要なのか?

現代のWebアプリケーションは、多くの場合、ブラウザで実行されるフロントエンド(HTML, CSS, JavaScript)と、サーバーで実行されるバックエンドAPIで構成されています。これらのフロントエンドとバックエンドが異なるドメイン、ポート、またはプロトコルで動作している場合、ブラウザのセキュリティ機能によって、あるオリジン(源)から別のオリジンへのHTTPリクエストが制限されます。このセキュリティメカニズムを「同一オリジンポリシー (Same-Origin Policy – SOP)」と呼びます。

SOPは、悪意のあるウェブサイトが、ユーザーがログインしている別のサイト(例えば銀行サイトやメールサービス)から、ユーザーの許可なくデータを読み取ったり操作したりすることを防ぐための非常に重要なセキュリティ機能です。例えば、悪意のあるサイト evil.com が、ユーザーが bank.com にログインしている間に、bank.com のAPIに対してGETリクエストを送信し、ユーザーの口座情報を盗み出す、といったクロスサイトスクリプティング (XSS) やクロスサイトリクエストフォージェリ (CSRF) といった攻撃を防ぐ役割を果たします。

しかし、SOPは現代のWebアプリケーション開発において、意図的に異なるオリジン間でリソースを共有したい場合に問題となります。例えば、frontend.com でホストされているフロントエンドが、api.com でホストされているバックエンドAPIにアクセスしたい場合などです。このような正規のクロスオリジンリクエストを許可するためのメカニズムが、「Cross-Origin Resource Sharing (CORS)」です。

CORSは、サーバーがブラウザに対して「私のリソースは、これらの特定のオリジンからのリクエストに対して共有しても安全です」と明示的に伝えるための仕組みです。ブラウザは、サーバーからの応答に含まれる特定のHTTPヘッダーを確認し、リクエストを続行するかブロックするかを判断します。

FastAPIは、高速でモダンなPython Webフレームワークであり、API開発に最適です。FastAPIでは、uvicorn などのASGIサーバー上で動作し、ミドルウェアを利用して様々なリクエスト処理を簡単に追加できます。CORSへの対応も、FastAPIの標準ミドルウェアとして提供されており、非常に簡単に設定できます。

本記事では、FastAPIにおけるCORS設定について、その基本的な仕組みから、詳細な設定方法、よくあるエラーとその対応策、そしてセキュリティ上の考慮事項まで、網羅的に解説します。

同一オリジンポリシー (SOP) とは?

CORSを理解するためには、まずSOPをしっかりと理解する必要があります。

「オリジン」とは、以下の3つの要素の組み合わせによって定義されます。

  1. スキーム (Scheme): プロトコル。例: http, https
  2. ホスト (Host): ドメイン名またはIPアドレス。例: example.com, localhost, 192.168.1.1
  3. ポート番号 (Port): サーバーのポート番号。例: 80, 443, 8080, 3000

これら3つの要素がすべて一致する場合にのみ、同一オリジンと見なされます。一つでも異なる場合、異なるオリジンと見なされます。

例:
http://example.com/path/to/page (スキーム: http, ホスト: example.com, ポート: 80 – httpのデフォルト)
http://example.com:80/other/path (スキーム: http, ホスト: example.com, ポート: 80)
https://example.com/secure (スキーム: https, ホスト: example.com, ポート: 443 – httpsのデフォルト)
http://sub.example.com/ (スキーム: http, ホスト: sub.example.com, ポート: 80)
http://example.com:8080/ (スキーム: http, ホスト: example.com, ポート: 8080)
http://example.org/ (スキーム: http, ホスト: example.org, ポート: 80)

上のリストで、最初の二つ (http://example.com/path/to/pagehttp://example.com:80/other/path) は同一オリジンです。しかし、それ以外のURLは最初のURLとは異なるオリジンになります。スキームが違う (https)、ホストが違う (sub.example.com, example.org)、ポートが違う (8080) のいずれかに該当するためです。

ブラウザのSOPは、デフォルトでは、あるオリジンから取得したドキュメントやスクリプトが、異なるオリジンからリソースを読み取ること(HTTP GETリクエストなど)を制限します。特定のケース(例: CSS, images, scriptタグ、form submissionなど)ではクロスオリジンアクセスが許可されますが、これは主にリソースの埋め込み送信に関するもので、応答の内容をスクリプトから読み取ることではありません。スクリプトによるクロスオリジンからの応答の読み取りは、SOPによってブロックされます。

CORS (Cross-Origin Resource Sharing) の仕組み

CORSは、サーバーが異なるオリジンからのスクリプトによるリソース読み取りリクエストを許可するための標準的なメカニズムです。これは、サーバーが特定のHTTPレスポンスヘッダーを返すことによって機能します。ブラウザはこれらのヘッダーを読み取り、リクエストを続行するか、またはSOP違反としてブロックするかを判断します。

CORSリクエストには主に2つのタイプがあります。

  1. 単純リクエスト (Simple Requests)
  2. プリフライトリクエスト (Preflighted Requests)

単純リクエスト (Simple Requests)

特定の条件を満たすリクエストは、「単純リクエスト」と見なされ、プリフライトリクエストなしに直接サーバーに送信されます。これらの条件は以下の通りです。

  • メソッドが以下のいずれかであること:
    • GET
    • HEAD
    • POST
  • ヘッダーが、ブラウザによって自動的に設定されるヘッダー(例: User-Agent, Accept-Language, Content-Languageなど)または、以下のヘッダーのみであること:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • Range
  • Content-Type ヘッダーの値が以下のいずれかであること:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • ReadableStream オブジェクトがリクエストに使用されていないこと。

単純リクエストの場合、ブラウザはリクエストを直接サーバーに送信し、リクエストヘッダーに Origin ヘッダーを含めます。このヘッダーには、リクエストを送信しているページのオリジンが含まれます(例: Origin: http://frontend.com).

サーバーがこのリクエストを受け取り、クロスオリジンからのアクセスを許可する場合、応答ヘッダーに Access-Control-Allow-Origin ヘッダーを含めて返します。このヘッダーの値は、許可するオリジンを指定します。

“`http
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://frontend.com
Access-Control-Allow-Credentials: true # もしCredentialsが許可されている場合
Content-Length: …

{
“data”: “some data”
}
“`

ブラウザはサーバーからの応答を受け取ると、応答ヘッダーの Access-Control-Allow-Origin の値を確認します。
– 値がリクエストの Origin ヘッダーの値と一致する場合、ブラウザは応答をJavaScriptに公開します。
– 値が * の場合(ただし Access-Control-Allow-Credentialstrue でない場合)、ブラウザは応答をJavaScriptに公開します。
– それ以外の場合、ブラウザはSOP違反とみなし、応答をJavaScriptに公開せず、コンソールにCORSエラーを表示します。

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

単純リクエストの条件を満たさないリクエスト(例: PUT, DELETE メソッド、またはカスタムヘッダーを含むリクエストなど)は、「プリフライトリクエスト」が必要になります。

プリフライトリクエストでは、実際のHTTPリクエストを送信する前に、ブラウザはサーバーに対して OPTIONS メソッドを使用した「事前確認」リクエストを送信します。この OPTIONS リクエストには、ブラウザが送信しようとしている実際のリクエストに関する情報(メソッド、ヘッダーなど)が含まれます。

OPTIONS リクエストの例:

http
OPTIONS /api/resource HTTP/1.1
Host: api.com
Origin: http://frontend.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header, Content-Type

サーバーは OPTIONS リクエストを受け取ると、そのオリジン (Origin)、メソッド (Access-Control-Request-Method)、およびヘッダー (Access-Control-Request-Headers) に対して、実際のリクエストを許可するかどうかを判断し、その結果を応答ヘッダーでブラウザに伝えます。

サーバーの OPTIONS 応答ヘッダーの例:

“`http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: X-Custom-Header, Content-Type, Authorization
Access-Control-Allow-Credentials: true # もしCredentialsが許可されている場合
Access-Control-Max-Age: 86400 # プリフライト結果をキャッシュする秒数

“`

ブラウザはサーバーからの OPTIONS 応答を受け取ると、以下のヘッダーを確認します。

  • Access-Control-Allow-Origin: リクエストの Origin ヘッダーの値が許可されているか。
  • Access-Control-Allow-Methods: 実際のメソッド (PUT) が許可されているか。
  • Access-Control-Allow-Headers: 実際のリクエストに含まれるすべてのカスタムヘッダー (X-Custom-Header, Content-Type) が許可されているか。
  • Access-Control-Allow-Credentials: 資格情報(クッキー、認証ヘッダーなど)を送信できるか。

これらのヘッダーの内容がブラウザが送信しようとしている実際のリクエストを許可する内容であれば、ブラウザは続いて実際のHTTPリクエスト(この例では PUT /api/resource)を送信します。
もし許可されない内容であれば、ブラウザはSOP違反とみなし、実際のリクエストを送信せずにコンソールにCORSエラーを表示します。

プリフライトリクエストは、ブラウザがサーバーに余計な負荷をかけることなく、潜在的に問題のあるクロスオリジンリクエストを事前にチェックするための安全対策です。Access-Control-Max-Age ヘッダーを使用することで、プリフライトの結果を一定期間キャッシュし、同じオリジンからの後続の同じ種類のリクエストに対してプリフライトを省略することができます。

FastAPI で CORS を設定する

FastAPIでは、fastapi.middleware.cors.CORSMiddleware を使用することで、簡単にCORSポリシーを設定できます。これは ASGIミドルウェアとして実装されており、アプリケーションへのリクエストが実際のルートハンドラに到達する前にCORSチェックを行います。

CORSMiddleware は、FastAPIアプリケーションオブジェクトに対して add_middleware メソッドを使って追加します。

基本的な設定方法は以下の通りです。

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

app = FastAPI()

許可するオリジン(フロントエンドのURLなど)

環境に応じて変更してください

origins = [
“http://localhost”,
“http://localhost:8000”, # FastAPIのデフォルトポート
“http://localhost:3000”, # React/Vue/Angularなどのデフォルトポート
“https://your-production-frontend.com”,
# 将来的に追加されるオリジンがあればここに追加
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=[““], # 全てのHTTPメソッドを許可する場合
allow_headers=[“
“], # 全てのHTTPヘッダーを許可する場合
)

@app.get(“/items/”)
async def read_items():
return [{“name”: “Foo”}, {“name”: “Bar”}]

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

@app.put(“/items/{item_id}”)
async def update_item(item_id: int, item: dict):
return {“item_id”: item_id, “item”: item}
“`

このコードでは、CORSMiddleware をアプリケーションに追加し、いくつかの重要なパラメータを設定しています。それぞれのパラメータについて詳しく見ていきましょう。

allow_origins

このパラメータは、どのオリジンからのリクエストを許可するかを指定します。最も重要かつ基本的なパラメータです。

  • リストで指定: 最も推奨される方法です。許可したいオリジンを文字列のリストとして指定します。
    python
    origins = [
    "http://localhost:3000",
    "https://my-frontend.com",
    ]
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    # ... other parameters
    )

    この設定では、http://localhost:3000https://my-frontend.com からのリクエストのみが許可されます。それ以外のオリジンからのリクエストはブラウザによってブロックされます。

  • "*" で全て許可: allow_origins=["*"] と設定すると、全てのオリジンからのリクエストを許可します。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    # ... other parameters
    )

    この設定は開発環境では便利ですが、本番環境での使用は慎重に行うべきです。特に、後述する allow_credentials=True と組み合わせて使用することは絶対に避けるべきです。理由については後述します。APIがパブリックで、機密情報を含まないデータを返すだけであれば問題ない場合もありますが、セキュリティ上のリスクを十分に理解しておく必要があります。

  • 正規表現で指定 (allow_origin_regex): 複雑なオリジンパターン(例: すべてのサブドメイン)を許可したい場合は、allow_origins の代わりに allow_origin_regex パラメータに正規表現パターンを指定できます。
    “`python
    import re

    example.com およびそのすべてのサブドメインを許可

    httpおよびhttpsの両方を許可

    app.add_middleware(
    CORSMiddleware,
    allow_origin_regex=”https?://(.*\.)?example\.com”,
    # … other parameters
    )
    ``allow_originsallow_origin_regex` は同時に指定できません。どちらか一方を使用してください。

allow_credentials

このパラメータは、クロスオリジンリクエストでクッキー、認証ヘッダー(例: Authorization ヘッダー)、またはTLSクライアント証明書などの「資格情報 (Credentials)」を送信することを許可するかどうかを指定します。デフォルトは False です。

  • allow_credentials=True: 資格情報の送信を許可します。これは、セッション管理にクッキーを使用したり、トークンベースの認証(例: Bearerトークン)を使用する場合に必要になります。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"], # Specific origins MUST be listed
    allow_credentials=True, # Allow cookies, auth headers, etc.
    # ... other parameters
    )

    重要: allow_credentials=True を設定する場合、allow_origins"*" を指定することはできません。許可するオリジンを明示的なリストまたは正規表現 (allow_origin_regex) で指定する必要があります。これはセキュリティ上の制約です。もし allow_origins=["*"]allow_credentials=True を同時に設定しようとすると、ブラウザはCORSリクエストをブロックします。これは、任意のオリジンから資格情報付きでリクエストが送信できてしまうと、CSRF攻撃などのリスクが高まるためです。サーバーは Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true の両方を返すことが標準で禁止されています。

allow_methods

このパラメータは、クロスオリジンリクエストで許可するHTTPメソッドを指定します。デフォルトは ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'] です。

  • リストで指定: 許可したいメソッドを文字列のリストとして指定します。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_methods=["GET", "POST"], # Only GET and POST allowed
    # ... other parameters
    )
  • "*" で全て許可: allow_methods=["*"] と設定すると、すべての標準的なHTTPメソッドを許可します。これはデフォルトのリストと同じ効果です。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_methods=["*"], # Allow all default methods
    # ... other parameters
    )

    通常、特に制限する必要がなければ ["*"] またはデフォルトを使用すれば十分です。

allow_headers

このパラメータは、クロスオリジンリクエストで許可するHTTPリクエストヘッダーを指定します。ブラウザは特定のヘッダー(例: Accept, Content-Language, Content-Type など、前述の単純リクエストで許可されているもの)を常に含めますが、これ以外のカスタムヘッダー(例: X-Custom-Header, Authorization)を送信する場合、ここで明示的に許可する必要があります。デフォルトは空のリスト [] ですが、これはブラウザが自動的に許可するヘッダー以外は許可しないという意味になります。

  • リストで指定: 許可したいヘッダー名を文字列のリストとして指定します。ヘッダー名は大文字・小文字を区別しませんが、標準的にはハイフン区切り小文字 (content-type) またはキャメルケース (ContentType) が使われます。ミドルウェアは内部的に適切に処理します。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_headers=["X-Custom-Header", "Authorization", "Content-Type"], # Allow these specific headers
    # ... other parameters
    )
  • "*" で全て許可: allow_headers=["*"] と設定すると、ブラウザが送信する可能性のあるすべてのヘッダーを許可します。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_headers=["*"], # Allow all headers
    # ... other parameters
    )

    allow_headers=["*"] は、開発環境では便利ですが、本番環境では必要なヘッダーのみを明示的にリストすることをお勧めします。これは、悪意のあるヘッダーを介した攻撃のリスクを減らすためです。特に Authorization ヘッダーをクロスオリジンで受け付ける場合は、allow_credentials=True と共に、オリジン制限を厳格に適用する必要があります。

expose_headers

このパラメータは、ブラウザのJavaScriptコードからアクセスできるようになる、デフォルト以外の応答ヘッダーを指定します。デフォルトでは、JavaScriptは特定の安全な応答ヘッダー(例: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma)のみにアクセスできます。サーバーがこれらのヘッダー以外にカスタムヘッダーを応答に含め、それをフロントエンドのJavaScriptで読みたい場合、ここで指定する必要があります。デフォルトは空のリスト [] です。

  • リストで指定: 公開したい応答ヘッダー名を文字列のリストとして指定します。
    python
    # 例:カスタムヘッダー 'X-Total-Count' をJavaScriptから読めるようにする
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    expose_headers=["X-Total-Count"],
    # ... other parameters
    )

max_age

このパラメータは、プリフライトリクエスト (OPTIONS) の結果をブラウザがキャッシュできる最大秒数を指定します。デフォルトは 600 秒(10分)です。

  • 秒数で指定: キャッシュしたい秒数を整数で指定します。
    python
    app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    max_age=3600, # Cache preflight results for 1 hour
    # ... other parameters
    )

    max_age を長く設定すると、同じオリジンからの繰り返しリクエストに対するプリフライトリクエストの数を減らし、パフォーマンスを向上させることができます。しかし、CORS設定を変更した場合、ブラウザが古いキャッシュを使用している間は新しい設定が反映されない可能性があるため注意が必要です。

パラメータのまとめと推奨設定

パラメータ デフォルト 説明 推奨される設定
allow_origins List[str] [] 許可するオリジンのリスト。"*" で全て許可 (非推奨)。allow_origin_regex とは併用不可。 本番環境では、アプリケーションが実際にアクセスされる予定のオリジンを明示的にリストする。ローカル開発では "http://localhost:ポート番号" などを追加。
allow_origin_regex Optional[str] None 許可するオリジンを定義する正規表現パターン。allow_origins とは併用不可。 複数のサブドメインなど、複雑なオリジンパターンを許可したい場合に検討。
allow_credentials bool False 資格情報(クッキー、認証ヘッダーなど)の送信を許可するか。 認証にクッキーやAuthorizationヘッダーを使用する場合はTrueに設定。allow_origins"*"の場合はTrueにできない
allow_methods List[str] ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'] 許可するHTTPメソッドのリスト。"*" で全て許可。 通常はデフォルトまたは["*"]で十分。必要に応じて制限。
allow_headers List[str] [] 許可するリクエストヘッダーのリスト。"*" で全て許可。 必要なカスタムヘッダー(例: Authorization, X-Requested-With など)を明示的にリスト。本番環境で"*"は非推奨。
expose_headers List[str] [] ブラウザのJavaScriptからアクセス可能な応答ヘッダーのリスト。 JavaScriptから読みたいカスタム応答ヘッダーがある場合にリストする。
max_age int 600 (10分) プリフライトリクエストの結果をキャッシュする秒数。 通常はデフォルトで十分だが、プリフライトが多い場合はパフォーマンスのために長くしても良い (例: 3600 = 1時間)。

ミドルウェアの順番

FastAPIでは複数のミドルウェアをチェインできます。CORSMiddleware は通常、認証ミドルウェアなどよりも前に配置します。これは、CORSチェックは認証よりも低レベルの処理であり、不正なオリジンからのリクエストは認証レイヤーに到達する前にブロックされるべきだからです。add_middleware に渡されるミドルウェアは、リストの最後に追加されたものから順に実行されます(内側から外側へ)。したがって、CORSMiddleware を最初に add_middleware で追加するか、リストの最後に指定することで、他のミドルウェアより外側(リクエスト処理の初期段階)で実行されるようにします。

“`python
app = FastAPI()

CORSMiddleware を最初に追加 (add_middleware呼び出し順の最初、実行順の外側)

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

例えば、その後に認証ミドルウェアを追加

from fastapi.middleware.authentication import AuthenticationMiddleware # 例

app.add_middleware(AuthenticationMiddleware, backend=SomeAuthBackend())

@app.get(“/protected/”)
async def read_protected(current_user: str = Depends(get_current_user)): # 認証が必要なルート
return {“message”: f”Hello {current_user}, this is protected data”}

@app.get(“/public/”)
async def read_public(): # 認証が不要なルート
return {“message”: “This is public data”}

``
この例では、CORSチェックが認証チェックよりも先に実行されます。不正なオリジンからの
/protected/` へのリクエストは、CORSミドルウェアによってブロックされ、認証ミドルウェアには到達しません。

CORS エラーとトラブルシューティング

CORSはWeb開発者にとってよくある落とし穴の一つです。正しく設定されていない場合、ブラウザのコンソールにエラーが表示され、APIへのリクエストが失敗します。ここでは、よくあるCORSエラーとそのトラブルシューティング方法について解説します。

CORS関連のエラーは、ブラウザのコンソールで確認するのが基本です。サーバー側のログには、通常、リクエストがミドルウェアでブロックされたという直接的なエラーメッセージは表示されません(リクエストがアプリケーションコードまで到達しないため)。

よくあるエラーメッセージ例 (ブラウザコンソール)

  1. “Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”

    • 意味: クライアントのオリジンからのリクエストに対して、サーバーからの応答に Access-Control-Allow-Origin ヘッダーが含まれていませんでした。これは、サーバーがCORSを有効にしていないか、またはクライアントのオリジンを許可するように設定されていないことを意味します。
    • 原因:
      • FastAPIアプリケーションに CORSMiddleware が追加されていない。
      • allow_origins (または allow_origin_regex) にクライアントのオリジンが含まれていない。
      • ミドルウェアの順番がおかしいなどで、CORSMiddleware が正しく機能していない。
    • 対応策:
      • app.add_middleware(CORSMiddleware, ...) がFastAPIアプリケーションの定義後に正しく記述されているか確認してください。
      • allow_origins リストに、リクエストを発行しているフロントエンドの正確なオリジン(スキーム、ホスト、ポートを含む)が含まれているか確認してください。例: フロントエンドが http://localhost:3000 で動いているなら、origins = ["http://localhost:3000"] のようにリストに含めます。
      • 注意: http://localhosthttp://localhost:8000 は異なるオリジンです。ポート番号も正確に指定する必要があります。http://example.comhttps://example.com も異なるオリジンです。スキームも確認してください。
      • もし正規表現を使っているなら、allow_origin_regex が正しくクライアントのオリジンにマッチするか確認してください。
  2. “Access to fetch at ‘…’ from 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) が成功しませんでした。サーバーが OPTIONS リクエストに対して 2xx 系のステータスコード(通常は 200 OK)を返しませんでした。
    • 原因:
      • サーバーサイドで OPTIONS メソッドが許可されていない、または CORSMiddlewareOPTIONS リクエストを処理できていない。
      • FastAPIアプリケーションのルーティングが、OPTIONS リクエストを処理しようとしてエラーになっている(例: 認証ミドルウェアなどがCORSミドルウェアより前にあり、OPTIONS リクエストに対して認証を要求して失敗している)。
      • APIエンドポイントが存在しないパスに対してリクエストしている(404 Not Foundが返ってくる)。
    • 対応策:
      • allow_methods=["*"] または allow_methods=["OPTIONS", ...] のように OPTIONS メソッドが許可されていることを確認します。CORSMiddleware はデフォルトで OPTIONS を許可しますが、明示的にリストを指定している場合は注意が必要です。
      • CORSMiddleware が認証ミドルウェアなどの他のミドルウェアより外側(前)で実行されるように、add_middleware の呼び出し順を確認します。CORSMiddleware は通常、他のミドルウェアが処理を開始する前に OPTIONS リクエストに応答する必要があります。
      • ブラウザの開発者ツールで、実際に送られている OPTIONS リクエストとその応答を確認します。応答のステータスコードが200番台以外であれば、サーバー側で何かエラーが発生している可能性があります。
  3. “Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: Request header field ‘X-Custom-Header’ is not allowed by Access-Control-Allow-Headers in preflight response.”

    • 意味: プリフライトリクエスト (OPTIONS) の応答で、クライアントが送信しようとしているカスタムヘッダー(この例では X-Custom-Header)が Access-Control-Allow-Headers ヘッダーによって許可されていません。
    • 原因:
      • allow_headers に、クライアントが送信しているカスタムヘッダー名が含まれていない。
    • 対応策:
      • クライアントが送信しているすべてのカスタムヘッダー名を特定し、allow_headers リストに含めます。例: allow_headers=["X-Custom-Header", "Authorization", "Content-Type"]
      • 開発中は手っ取り早く allow_headers=["*"] としても良いですが、本番環境では必要なヘッダーのみをリストすることを検討してください。
  4. “Access to fetch at ‘…’ from 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’.”

    • 意味: クライアントが資格情報(クッキーなど)を含めてリクエストを送信しているにも関わらず、サーバーからの応答の Access-Control-Allow-Origin ヘッダーが * になっています。これはセキュリティ上の制約により許可されません。
    • 原因:
      • クライアント側で credentials: 'include' または同等の設定(例: withCredentials = true)でリクエストを送信している。
      • サーバー側で allow_origins=["*"] と設定している。
    • 対応策:
      • サーバー側で allow_origins"*" ではなく、具体的なオリジンリストまたは正規表現に変更します。例: allow_origins=["http://localhost:3000", "https://your-frontend.com"]
      • または、もし資格情報が本当に不要であれば、クライアント側で資格情報を含めないように設定を変更します(credentials: 'omit' または credentials: 'same-origin')。ただし、通常API認証には資格情報が必要なので、サーバー側の設定変更が適切な場合が多いです。
  5. その他のエラー: 特定のメソッドが許可されていない、特定の公開ヘッダーにアクセスできないなど、他のエラーメッセージが表示されることもあります。これらの場合も、メッセージの内容から原因(allow_methods, expose_headers の設定不足など)を推測し、対応するパラメータを修正します。

トラブルシューティングの手順

CORSエラーに遭遇した場合、以下の手順で調査・解決を進めるのが効果的です。

  1. ブラウザコンソールを確認する: 最も重要なステップです。表示されているCORSエラーメッセージを正確に読み取ります。どのオリジンからのリクエストが、どのオリジンにあるリソースに対して、なぜブロックされたのか(Access-Control-Allow-Origin が無い/不正、プリフライト失敗、ヘッダー/メソッドが許可されていない、資格情報と * の組み合わせなど)の情報が含まれています。
  2. ブラウザの開発者ツール(ネットワークタブ)を使用する:
    • エラーが発生しているAPIリクエストを探します。
    • そのリクエストをクリックし、詳細を確認します。
    • 特に重要なのは「ヘッダー (Headers)」タブです。
      • リクエストヘッダー (Request Headers): Origin ヘッダーが正しく送信されているか確認します。また、送信されているカスタムヘッダーやメソッド(特にプリフライトが必要なリクエスト)を確認します。
      • 応答ヘッダー (Response Headers): サーバーからの応答に含まれる Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Expose-Headers, Access-Control-Max-Age などのヘッダーが期待通りになっているか確認します。
    • プリフライトリクエスト (OPTIONS) を確認する: もしプリフライトが必要なリクエストの場合、実際のAPIリクエストの前に OPTIONS リクエストが送信されているはずです。この OPTIONS リクエストとその応答を特に詳しく調べます。OPTIONS 応答のステータスコードが200番台であること、そして応答ヘッダーが後続の実際のリクエスト(メソッド、ヘッダー、資格情報)を許可する内容になっていることを確認します。
  3. FastAPIサーバーのCORS設定コードを確認する:
    • CORSMiddleware が正しくインポートされ、アプリケーションに追加されているか (app.add_middleware)。
    • allow_origins: クライアントのオリジンがリストに含まれているか、または正規表現にマッチするか。ポート番号やスキーム(http/https)も含めて正確に確認します。末尾のスラッシュなども含めて完全に一致させる必要がある場合もありますが、通常オリジンはスキーム://ホスト[:ポート] の形式で指定します。
    • allow_credentials: クライアントが資格情報付きリクエストを送信している場合、これが True になっているか。その場合、allow_origins"*" ではないか。
    • allow_methods: 使用しているHTTPメソッドが許可されているか。
    • allow_headers: 使用しているカスタムヘッダーが許可されているか。
    • expose_headers: JavaScriptから読みたい応答ヘッダーが指定されているか。
    • ミドルウェアの順番を確認します。CORSMiddleware は通常、他の処理より前に来るようにします。
  4. FastAPIサーバーを再起動する: 設定変更を反映するには、サーバーの再起動が必要です。
  5. クライアント側の設定を確認する: JavaScriptの fetchXMLHttpRequest などでリクエストを送信する際、credentials: 'include'withCredentials = true などの設定が意図通りになっているか確認します。
  6. シンプルなケースで試す: 問題解決のために、一時的に allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] と設定し、allow_credentials=False にして試してみます。もしこの設定でCORSエラーが解消されるなら、問題は特定のオリジン、メソッド、ヘッダー、または資格情報の扱いにあります。そこから徐々に設定を絞り込んで、何が不足しているか特定します。ただし、この設定を本番環境に残さないように十分に注意してください。
  7. プロキシ設定を確認する: もしリバースプロキシ(Nginx, Apacheなど)を介してFastAPIアプリケーションを公開している場合、プロキシ側でCORS関連のヘッダーを誤って上書きしたり削除したりしていないか確認します。FastAPIのCORSミドルウェアが生成したヘッダーが、クライアントに正しく到達している必要があります。

セキュリティ上の考慮事項

CORSはクロスオリジンアクセスを可能にするためのものですが、設定を誤るとセキュリティリスクを招く可能性があります。特に以下の点に注意が必要です。

  • allow_origins=["*"] の使用:
    • 危険性: 資格情報 (allow_credentials=True) と組み合わせて使用すると、インターネット上の任意のウェブサイトから、ユーザーのクッキーや認証情報を使用して認証済みリクエストをAPIに送信できてしまいます。これにより、CSRF攻撃などのリスクが著しく高まります。
    • 例外: APIが完全にパブリックで、認証を必要とせず、機密情報を含まないデータのみを扱う場合は、セキュリティリスクは比較的低いと考えられます。しかし、それでも意図しない第三者からのアクセスを完全に排除したい場合は、やはりオリジンを制限すべきです。
    • 推奨: 本番環境では、必ずアクセス元となるオリジンを具体的なリストで指定してください。ローカル開発環境でのみ、一時的に便宜上 "*" を使用することを検討してください。
  • allow_methods=["*"] および allow_headers=["*"] の使用:
    • 危険性: 必要以上に多くのメソッドやヘッダーを許可することは、攻撃ベクターを増やす可能性があります。例えば、許可すべきでないメソッド(例: DELETE)や、悪用される可能性のあるカスタムヘッダーを許可してしまうリスクがあります。
    • 推奨: 実際にアプリケーションで使用するメソッドとヘッダーのみを許可リストに含めるのがベストプラクティスです。しかし、多くの場合は allow_methods=["*"] は問題ありません(標準的なHTTPメソッドのみが許可されるため)。allow_headers については、Authorization などの認証関連ヘッダーやカスタムヘッダーを明示的にリストすることをお勧めします。
  • allow_credentials=True の使用:
    • 危険性: これを allow_origins=["*"] と組み合わせて使用した場合のリスクは前述の通り非常に高いです。また、allow_credentials=True を使用すると、Access-Control-Allow-Origin* が設定できなくなるという制約があるため、誤って設定するとブラウザがリクエストをブロックします。
    • 推奨: 認証にクッキーや認証ヘッダーが必要な場合にのみ True に設定します。そして、必ず allow_origins を具体的なオリジンリストで指定してください。

セキュリティの原則として、「最小権限の原則」を適用することが重要です。つまり、必要最低限のオリジン、メソッド、ヘッダーのみを許可するようにCORSを設定します。

発展的な話題

動的なオリジン許可

許可するオリジンが環境や設定ファイルによって変わる場合、FastAPIアプリケーションの起動時にそれらを読み込んで origins リストを構築するのが一般的な方法です。

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

環境変数から許可オリジンを読み込む例

例: COMMA_SEPARATED_ORIGINS=”http://localhost:3000,https://prod-frontend.com”

allowed_origins_str = os.environ.get(“ALLOWED_ORIGINS”, “”)
origins = [origin.strip() for origin in allowed_origins_str.split(“,”) if origin.strip()]

デフォルトや開発用のオリジンを追加することも可能

if not origins:
origins = [
“http://localhost”,
“http://localhost:8000”,
“http://localhost:3000”,
]

print(f”Configured CORS origins: {origins}”)

app = FastAPI()

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

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

このようにすることで、デプロイ環境に合わせて許可オリジンを簡単に変更できます。

カスタムCORSロジック

CORSMiddleware は一般的なCORS設定には十分ですが、例えばリクエストのヘッダーやパスに基づいて動的に許可するオリジンを決定したい、といったより複雑な要件がある場合は、独自のASGIミドルウェアを作成するか、FastAPIの依存性注入システムを使ってCORSヘッダーを自分で設定するなどの方法が考えられます。しかし、これはかなりの複雑さを伴うため、通常は CORSMiddleware の設定で対応することをお勧めします。allow_origin_regex で対応できる範囲であれば、それを利用するのが良いでしょう。

FastAPI Router と CORS

CORSMiddleware は通常、FastAPIアプリケーションインスタンス(app = FastAPI())に対して一度だけ追加します。これにより、アプリケーション全体でCORSポリシーが適用されます。ルーター(APIRouter)ごとに異なるCORS設定を適用することは、CORSMiddleware の標準的な使い方ではありません。もし異なるCORSポリシーを適用したいAPIパス群がある場合は、アプリケーションを分割するか、カスタムミドルウェアでパスごとの処理を実装するなどの方法が必要になりますが、これは稀なケースです。多くの場合、アプリケーション全体で単一のCORSポリシーを適用することで十分です。

まとめ

CORSは、ブラウザのセキュリティ機能である同一オリジンポリシー (SOP) に基づく、クロスオリジンリソース共有のための重要なメカニズムです。サーバーは特定のHTTPレスポンスヘッダーを返すことで、どのオリジンからのリクエストを許可するかをブラウザに伝えます。

FastAPIは、fastapi.middleware.cors.CORSMiddleware を提供することで、CORS設定を非常に簡単に行えます。allow_origins, allow_credentials, allow_methods, allow_headers などのパラメータを設定することで、柔軟かつセキュアなCORSポリシーを定義できます。

CORS関連のエラーは、主にブラウザのコンソールと開発者ツールのネットワークタブを使ってトラブルシューティングします。特にプリフライトリクエスト (OPTIONS) の応答ヘッダーを確認することが重要です。

セキュリティ上の観点から、本番環境では allow_origins=["*"] の使用を避け、資格情報 (allow_credentials=True) を許可する場合は、許可オリジンを厳密にリストアップすることが極めて重要です。必要最低限のメソッドとヘッダーのみを許可するように設定することで、アプリケーションのセキュリティを向上させることができます。

FastAPIの強力なミドルウェアシステムを活用することで、CORSの複雑さを吸収し、安全で効率的なAPI開発に集中できます。本記事が、FastAPIにおけるCORSの理解と適切な設定に役立つことを願っています。


コメントする

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

上部へスクロール