Python | Web フレームワーク:CORS

Python
スポンサーリンク

概要(CORS は「別ドメインからの JS リクエストを許可するルール」)

CORS(Cross-Origin Resource Sharing)は、

「ブラウザ上の JavaScript が、別のドメイン(オリジン)のサーバーにアクセスしていいかどうか」

を決めるルールです。

例えば、

フロントエンド:http://localhost:3000(React など)
バックエンド API:http://localhost:8000(FastAPI)

という構成で、フロントから API を叩こうとすると、
ブラウザが「これ、別オリジンだけど本当にアクセスしていいの?」と確認を始めます。
このとき適切な CORS 設定がないと、
サーバー自体は応答していても、ブラウザ側でブロックされてしまいます。

CORS は「セキュリティのためのブラウザ側の制限」を、
サーバーが「このオリジンからのアクセスは OK ですよ」と解除してあげる仕組みです。

ここでは FastAPI を例に、
初心者向けに CORS のイメージと設定方法をかみ砕いて説明していきます。


まず「オリジン」の感覚をつかむ(なぜ同じ PC なのに“別”と扱われるのか)

オリジン=「スキーム+ホスト+ポート」の組み合わせ

CORS を理解するうえで、まず押さえたいのが「オリジン(origin)」です。

オリジンは、

スキーム(http / https)
ホスト(ドメイン名 / IP)
ポート(:80, :3000 など)

この 3つのセットです。

例えば、

http://localhost:3000

http://localhost:8000

は、ポートが違うので「別オリジン」です。

http://example.com

https://example.com

も、スキームが違うので別オリジンです。

ブラウザは「同一オリジンポリシー」といって、
「あるオリジン上で動いている JavaScript は、別オリジンのデータを勝手に読めない」
というルールを持っています。

これが CORS 制限の根っこにある考え方です。


CORS がないとどうなるか(よくある「フロントは動くのにブラウザが怒る」やつ)

サーバーはレスポンスを返しているのに、ブラウザコンソールにエラーが出る状態

よくある開発環境の構成で説明します。

フロント:http://localhost:3000
バックエンド API:http://localhost:8000

フロントからこんなコードで API を叩くとします。

fetch("http://localhost:8000/hello")
  .then(res => res.json())
  .then(console.log)
  .catch(console.error);
Python

FastAPI 側にはシンプルにこう書いてあるとします。

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
def hello():
    return {"message": "hello"}
Python

サーバー側のログを見ると、
確かに /hello にアクセスが来て、レスポンスも返っています。

しかし、ブラウザのコンソールにはこんな感じのエラーが出ます。

「Access to fetch at ‘http://localhost:8000/hello’
from origin ‘http://localhost:3000’ has been blocked by CORS policy:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.」

つまり、

サーバー「OK、レスポンス返したよ」
ブラウザ「CORS 的に NG だから、JavaScript からは見せないよ」

という状態です。

これを解決するのが CORS 設定です。


FastAPI での基本的な CORS 設定(CORSMiddleware)

CORSMiddleware を噛ませて「どのオリジンを許可するか」を宣言する

FastAPI では、CORS 対策はミドルウェアとして組み込みます。

最小の設定例は次のようになります。

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

app = FastAPI()

origins = [
    "http://localhost:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,            # 許可するオリジン
    allow_credentials=True,           # Cookie 等を許可するか
    allow_methods=["*"],              # 許可する HTTP メソッド
    allow_headers=["*"],              # 許可するヘッダ
)

@app.get("/hello")
def hello():
    return {"message": "hello"}
Python

この設定をすると、
http://localhost:3000 というオリジンからのリクエストには、
FastAPI がレスポンスに CORS 関連のヘッダを追加してくれます。

例えば、

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true

などです。

ブラウザはこのヘッダを見て、

「あ、このサーバーは localhost:3000 からのアクセスを許可しているんだな」

と判断し、JavaScript からレスポンスにアクセスできるようになります。

allow_origins に何を書けばいいか(ここが一番大事)

allow_origins には「どのオリジンからのアクセスを許可するか」のリストを渡します。

開発中によくやるのは、

React や Vue の開発サーバー → http://localhost:3000
バックエンド → http://localhost:8000

のような組み合わせなので、

origins = [
    "http://localhost:3000",
]
Python

と書いておけば十分です。

複数あれば増やしていきます。

origins = [
    "http://localhost:3000",
    "http://localhost:5173",  # Vite など
]
Python

allow_origins=["*"] として「全部許可」もできますが、
認証つきや本番環境では危険です。
開発中だけにしておく方が良いです。


プリフライトリクエスト(OPTIONS)と CORS(ちょっとだけ深掘り)

ブラウザが「本当にこのリクエストしていい?」と事前確認する仕組み

一部のリクエスト(特に、
GET 以外のメソッドや特殊なヘッダを使う場合)は、
ブラウザがいきなり本リクエストを送らず、
まず OPTIONS メソッドで「プリフライトリクエスト」を送ります。

イメージとしては、

ブラウザ「ねぇサーバー、このオリジンから、こういうメソッド・ヘッダで
アクセスしてもいい?」

サーバー「OK、その条件なら大丈夫だよ」

ブラウザ「じゃあ本物のリクエスト送るね」

という 2 段階です。

FastAPI の CORSMiddleware は、このプリフライト処理もやってくれます。

allow_methodsallow_headers に指定した内容が、
プリフライトのレスポンスヘッダ(Access-Control-Allow-Methods など)に反映されます。

例えば、

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Content-Type", "Authorization"],
)
Python

とすると、

「このオリジンからなら GET と POST を許可する」
「Content-Type と Authorization ヘッダも OK」

という意味になります。

フロント側が PUT や DELETE を使うなら、
ここに追加する必要があります。


allow_credentials と Cookie/認証との関係(ここもよくハマるポイント)

Cookie や認証情報を一緒に送りたい場合は特別な設定が必要

CORS では、「Cookie や認証情報が絡むリクエスト」は
少し扱いが厳しくなっています。

例えば、

フロント(ブラウザ)から axios や fetch でリクエストを送るときに、
Cookie を一緒に送ろうとすると、

fetch("http://localhost:8000/me", {
  credentials: "include",
});
Python

のように credentials: "include" を指定します。

この場合、サーバー側でも CORS 設定で

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
Python

と、allow_credentials=True にしておく必要があります。

さらにルールとして、

allow_credentials=True のときは
allow_origins=["*"] が使えません。

つまり、

「全部のオリジンから Cookie 付きリクエストを許可」
という危険な設定はできないようになっています。

この組み合わせはよくハマりポイントになるので、

Cookie/セッションを使う → allow_credentials=True + allow_origins は具体的に
トークンだけで、Cookie を使わない → allow_credentials=False でも問題なし

という風に整理しておくとよいです。


よくある失敗例と「こう考えると分かりやすい」視点

1. サーバーは動いているのに、フロントからだけエラーになる

バックエンドを curl や Postman で叩くと正常に動くのに、
ブラウザからの fetch だけエラー、という場合は、
ほぼ CORS です。

重要なのは、

CORS は「サーバーがリクエストを拒否している」のではなく、
「ブラウザがレスポンスを JavaScript に渡さないでブロックしている」

ということです。

なので、サーバーログだけ見ていると
「成功しているように見える」のに、
フロント側ではエラー、というズレが出ます。

「ブラウザコンソールに CORS エラーが出てないか」を必ず確認する習慣をつけると、
原因特定が早くなります。

2. とりあえず allow_origins=[“*”] にして解決したけど怖さが分からない

開発中なら正直、
allow_origins=["*"]
でさっさと CORS を通してしまうのもアリです。

ただ、本質的には

「どのオリジンからの JS に、自分の API へのアクセスを許すか」

という話なので、本番ではちゃんと絞るべきです。

例えば、自分のフロントエンドが

https://app.example.com

だけなら、

allow_origins=["https://app.example.com"]

のように限定します。

特に allow_credentials=True(Cookie 付き)を使う場合は、
必ず具体的なオリジンを書く必要があります。


まとめ(CORS は「ブラウザのガードマンに対する通行許可証」)

Python の Web フレームワーク(FastAPI)における CORS を、初心者目線で整理するとこうなります。

  • CORS は「ブラウザ上の JavaScript が別オリジンのサーバーにアクセスしていいか」を決める仕組みで、同じ PC 上でもポートやスキームが違えば別オリジンとして扱われる。
  • FastAPI では CORSMiddleware を使って allow_origins, allow_methods, allow_headers, allow_credentials を設定し、サーバー側から「このオリジンからのアクセスは OK」とヘッダで宣言する。
  • 開発でよくある構成(フロント: localhost:3000、API: localhost:8000)では、allow_origins=["http://localhost:3000"] を設定しないとブラウザが CORS エラーでブロックする。
  • Cookie や認証情報を含むリクエストには allow_credentials=True が必要で、その場合は allow_origins=["*"] は使えず、具体的なオリジンを指定しないといけない。
  • CORS エラーは「サーバーではなくブラウザがブロックしている」ので、バックエンドが正常に動いていてもフロント側だけエラーになることがある。ブラウザコンソールのメッセージと CORS 設定をセットで見る癖が重要。

タイトルとURLをコピーしました