FastAPIにおけるCORSのテスト方法

# 概要

- FastAPIで設定したCORSの自動テスト方法

# CORSについて

> オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組み

- [詳細](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)

# FastAPIにおけるCORS設定方法

- FastAPI アプリケーションでは CORSMiddleware を使用して、CORSに関する設定を実施
- [詳細](https://fastapi.tiangolo.com/ja/tutorial/cors/)

## CORSMiddlewareで設定可能な引数

|引数|内容|デフォルト|
|--|--|--|
|allow_origins|オリジン間リクエストを許可するオリジンのリスト||
|allow_origin_regex|オリジン間リクエストを許可するオリジンの正規表現文字列||
|allow_methods|オリジン間リクエストで許可するHTTPメソッドのリスト|['GET']|
|allow_headers|オリジン間リクエストでサポートするHTTPリクエストヘッダーのリスト<br>CORSリクエストでは、 Accept 、 Accept-Language 、 Content-Language 、 Content-Type ヘッダーが常に許可されている| [] |
|allow_credentials|オリジン間リクエストでCookieをサポートする必要があることを示します|False|
|expose_headers|ブラウザからアクセスできるようにするレスポンスヘッダーを示します|[]|
|max_age|ブラウザがCORSレスポンスをキャッシュする最大時間を秒単位で設定します|600|

```python: main.py
"""FastAPIのエントリーポイント."""
from typing import Dict

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

app = FastAPI()
"""FastAPIのインスタンス"""

# 本来なら環境変数から設定できるようにしておくといい
origins = ["http://allowed.origin.com"]

# CORSのミドルウェアを設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/")
async def root() -> Dict[str, str]:
    """ルートパス.

    Returns:
        messeageを返却
    """
    return {"message": "Hello World"}

```

# CORSの設定に対する自動テスト方法

- CORSプリフライトリクエストを使用してCORSの自動テストを実施
    - Origin ヘッダーと Access-Control-Request-Method ヘッダーを持つ OPTIONS リクエスト
    - ミドルウェアはリクエストを横取りし、適切なCORSヘッダーと共に情報提供のために 200 または 400 のレスポンスを返す
- [詳細](https://fastapi.tiangolo.com/ja/tutorial/cors/#cors_1)
- テスト方法
    - CORSプリフライトリクエストを設定
        - Originヘッダーにテストしたいオリジンを設定
        - Access-Control-Request-MethodにテストしたいHTTPメソッドを設定
    - OPTIONSリクエストを実施
    - HTTPステータスコードを確認
        - 200ならアクセス可能
        - 400ならアクセス不可能
    - レスポンスのヘッダーを確認
        - ステータスコードが200の場合
            - access-control-allow-originに送信したオリジンが設定されている
        - ステータスコードが400の場合
            - access-control-allow-originが設定されていないこと

```python: test_cors.py
"""CORSのテスト."""

from fastapi import status
from fastapi.testclient import TestClient
from pytest import fixture

# FastAPIのインスタンスをimport
from main import app


@fixture
def client():
    """テスト用のクライアント."""
    return TestClient(app)

def test_allowed_cors(client):
    """CORSに設定されているパスがアクセス可能なことをテスト."""
    url = "http://allowed.origin.com"
    headers = {
        "Origin": url,
        "Access-Control-Request-Method": "GET"
    }

    response = client.options("/", headers=headers)
    assert status.HTTP_200_OK == response.status_code
    assert response.headers["access-control-allow-origin"] == url

def test_disallowed_cors(client):
    """CORSに設定されてないパスがアクセス不可能なことをテスト."""
    url = "http://disallowed.origin.com"
    headers = {
        "Origin": url,
        "Access-Control-Request-Method": "GET"
    }

    response = client.options("/", headers=headers)
    assert status.HTTP_400_BAD_REQUEST == response.status_code
    assert "access-control-allow-origin" not in response.headers

```


# 参考

- [CORS (オリジン間リソース共有)](https://fastapi.tiangolo.com/ja/tutorial/cors/)
- [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)