「ブラウザでAPIを呼び出したら、なんかエラーが出た。コードは合ってるのに……」って経験、ある人はいるんじゃないかな。そのエラーのログに CORS って文字が出てたら、まさにこの記事がぴったりだよ。CORSって言葉、最初は呪文みたいに見えるけど、仕組みを知ればぜんぜん怖くない。この記事を読めば、「あーそういうことか!」って声に出したくなるくらいスッキリわかるよ。
- CORSは 違うオリジン(サイト)間でデータをやり取りするためのルール で、ブラウザが安全を守るために使っている。
- ブラウザには 同一オリジンポリシー というブロック機能があり、CORSはその「許可証」の仕組みにあたる。
- サーバーが Access-Control-Allow-Origin ヘッダー を返すことで、ブラウザがアクセスを許可する仕組みになっている。
もうちょっと詳しく
CORSのエラーが出ると、ブラウザのコンソールに「Cross-Origin Request Blocked」とか「has been blocked by CORS policy」みたいな文字が表示される。このとき、リクエスト自体はサーバーに届いていることも多いけど、ブラウザがレスポンスを「受け取るのを拒否」してるんだ。つまりエラーの原因はサーバーじゃなくて、ブラウザのセキュリティチェックにある。だからフロントエンド(ブラウザ側)だけをいじっても直らなくて、サーバー側の設定を変えないといけないことがほとんどだよ。これを知らずにフロントのコードをいじりまくって沼にはまる人がとても多いんだ。
CORSエラーはブラウザが出している。サーバー側の設定で直すのが基本!
⚠️ よくある勘違い
→ コードのミスではなく、サーバーのレスポンスヘッダーが足りないことが原因。フロントをどんなに直しても解決しない。
→ サーバーが
Access-Control-Allow-Origin ヘッダーを返すよう設定することで解決する。フロントエンドの問題ではない。
[toc]
CORSが生まれた背景―ブラウザはなぜ「壁」を作るのか
インターネットってすごく便利だけど、それだけ危険もある。たとえば、悪意のある人が作った罠サイトを開いたとき、そのページがこっそりあなたのネットバンキングにリクエストを送って、残高を調べたり、送金の命令を出したりできたら大変だよね。
ブラウザにはもともとその危険を防ぐしくみがある。それが 同一オリジンポリシー(Same-Origin Policy)、つまり「同じ場所からのリクエストしか許可しない」というルールだ。同一オリジンポリシーのおかげで、悪いサイトが勝手に別のサイトのデータを読み取ることができなくなっている。
でも「完全にブロック」じゃ不便すぎる
ただ、世の中には「あえて別のオリジンからデータを受け取りたい」という正当なケースが山ほどある。たとえば、フロントエンドのサイトが別のドメインにあるAPIサーバーからデータを取ってくるのは、今のWebアプリでは当たり前の話だ。「フロントは app.example.com、バックエンドAPIは api.example.com」みたいな構成はどこにでもある。
そこで登場したのがCORSという仕組みだ。「基本はブロックするけど、サーバーが明示的に許可を出したときだけ通す」というやり方で、セキュリティと利便性をバランスよく保てるようにした。ちょうど、学校の門が「基本は閉めてるけど、先生が名簿に書いた人だけ通す」みたいなイメージだよ。
「オリジン」ってどこまでが同じで、どこからが違う?
オリジンは プロトコル・ドメイン・ポート番号 の3つで決まる。この3つがぜんぶ同じなら「同じオリジン」、どれかひとつでも違えば「別のオリジン」になる。具体例を見てみよう。
https://example.comとhttps://example.com/page→ 同じオリジン(パスが違うだけ)https://example.comとhttp://example.com→ 別のオリジン(httpとhttpsで違う)https://example.comとhttps://api.example.com→ 別のオリジン(サブドメインが違う)https://example.comとhttps://example.com:3000→ 別のオリジン(ポート番号が違う)
「サブドメインが違うだけで別のオリジンになるの!?」って思うかもしれないけど、なる。これを知らずにハマる人がとても多いから、しっかり覚えておこう。
ブラウザが「許可証」を確認する方法―レスポンスヘッダーの話
CORSの仕組みの核心は、サーバーが返す HTTPレスポンスヘッダー にある。ヘッダーとは、データ本体とは別に「このデータはどういうものか」を説明するメモのようなもの。封筒の宛名書きみたいなイメージだ。
ブラウザが別のオリジンにリクエストを送ると、サーバーからレスポンスが返ってくる。そのレスポンスのヘッダーに Access-Control-Allow-Origin というフィールドがあれば、ブラウザはそれを見て「このオリジンからのアクセスを許可するよ」と判断する。
Access-Control-Allow-Origin の書き方
このヘッダーには値を設定できて、代表的な書き方は2パターンある。
Access-Control-Allow-Origin: *→ 「すべてのオリジンからのアクセスを許可する」という意味。ワイルドカード(つまり「なんでもOK」という記号)で、公開APIでよく使われる。ただし認証情報(Cookieなど)を含むリクエストには使えない制限がある。Access-Control-Allow-Origin: https://app.example.com→ 「このオリジンだけ許可する」という意味。特定のフロントエンドにだけ使わせたいAPIに使う。
ブラウザはリクエストを送るとき、自動的に Origin ヘッダーをリクエストに付けて送る。サーバーはそれを見て、許可するかどうかを判断してレスポンスヘッダーに返す。ブラウザがそのレスポンスヘッダーをチェックして、OKなら中身のデータをJavaScriptに渡す、NGならブロックする、という流れだ。
他にも関係するヘッダーがある
CORS関連のヘッダーは Access-Control-Allow-Origin だけじゃない。よく見るものをまとめると:
- Access-Control-Allow-Methods:許可するHTTPメソッド(GET、POST、DELETEなど)を指定する
- Access-Control-Allow-Headers:許可するリクエストヘッダーの種類を指定する
- Access-Control-Allow-Credentials:Cookieなどの認証情報を含めてよいか指定する
- Access-Control-Max-Age:後で出てくる「プリフライトリクエスト」の結果を何秒間キャッシュしてよいか指定する
これら全部を毎回気にしなくていいけど、複雑なリクエストを送るときはひとつひとつが関係してくる。
プリフライトリクエストって何をしてるの?
「シンプルなリクエスト」と「シンプルじゃないリクエスト」、この2種類があって、CORSの挙動が少し違うんだ。
シンプルなリクエストというのは、たとえばGETメソッドで普通のデータを取ってくるようなケース。この場合、ブラウザはそのままリクエストを送って、レスポンスのヘッダーを見て許可・拒否を判断する。
本番リクエストの前に「確認の使者」を送る
でも、POSTやDELETEなど「サーバーのデータを変える可能性があるリクエスト」や、特殊なヘッダーを含むリクエストの場合、ブラウザはいきなり本番のリクエストを送らない。先に プリフライトリクエスト(Preflight Request)、つまり「本番前の確認リクエスト」を送る。
プリフライトリクエストは OPTIONSメソッド を使って送られる。中身はざっくり「これからこういうリクエストを送ろうと思ってるんだけど、許可してくれる?」という問い合わせだ。サーバーがOKと答えたときだけ、本番のリクエストを送る。ちょうど、友達の家に遊びに行く前に「今日行っていい?」とLINEで確認してから実際に行くイメージ。
なぜプリフライトが必要なのか
サーバーのデータを変えるリクエストを最初から送ってしまうと、たとえブラウザが後でブロックしたとしても、サーバーのデータはもう変わってしまっている。それは困るから、「変える前に確認する」というステップを踏むわけだ。プリフライトがあれば、サーバーが許可しなければ本番リクエストは飛ばないので安全が保たれる。
開発者ツールのネットワークタブを見ると、プリフライトリクエストが OPTIONS として表示されているのが確認できるよ。「なぜか2回リクエストが飛んでる……?」と思ったらプリフライトが原因のことが多い。
CORSエラーが出たときの直し方
さて、実際にCORSエラーが出たらどうすればいいか。大事なのは「どこを直すか」を間違えないことだ。さっきも言ったけど、CORSエラーはブラウザのセキュリティチェックが原因だから、サーバー側の設定を変えるのが基本になる。
サーバー側で許可ヘッダーを追加する
一番よくある解決方法は、サーバーのレスポンスに Access-Control-Allow-Origin ヘッダーを追加すること。使っているサーバーやフレームワークによってやり方は違うけど、代表的な例を紹介するよ。
- Node.js(Express):
corsというパッケージを使うのが一番簡単。app.use(cors())と書くだけで全オリジンに許可が出る。 - Python(FastAPI):
CORSMiddlewareをアプリに追加して、許可するオリジンを設定する。 - Nginx・Apacheなどのウェブサーバー:設定ファイルに
add_header Access-Control-Allow-Origin "*";などを追記する。
開発中だけ使えるプロキシという方法もある
「サーバーのコードが触れない」「開発中だけ動かしたい」という場合は、フロントエンドの開発サーバーに プロキシ の設定を入れる方法もある。プロキシとは「代わりにリクエストを転送してくれる中継役」のこと。フロントから見ると「同じオリジンにリクエストを送った」ことになるから、CORSのチェックが発生しない。
たとえばViteやCreate React Appには開発用プロキシの設定機能がある。ただしこれはあくまで開発環境用の逃げ道で、本番環境ではサーバー側できちんとCORSを設定する必要があるから注意しよう。
やってはいけない「解決法」
ブラウザ拡張機能で「CORSを無効化する」ものがあるけど、これは自分のブラウザのセキュリティをオフにしてしまうものだから、開発中の一時的な確認以外には絶対に使わないようにしよう。本番でCORSを拒否されているなら、それはちゃんとした理由があるからだ。
CORSをもっと身近に感じるための具体例
最後に、日常のシーンに置き換えてCORSをもう一度整理してみよう。
図書館の「他の図書館から本を借りる」仕組みに似てる
あなたがA図書館に行って、「B図書館の本を読みたい」と言ったとする。A図書館がそのままB図書館に連絡して「この人に本を貸してあげて」と言えば、B図書館が許可して本が届く。でも、B図書館が「うちはA図書館経由での貸し出しは認めてません」と言ったら借りられない。
この「B図書館が許可を出すかどうか」がCORSのサーバー側の設定にあたる。B図書館がどんな条件で許可を出すかを決めているのと同じように、サーバーがどのオリジンからのアクセスを許可するかをヘッダーで伝えるんだ。
実際によくあるシーン
- React・Vue・Angularなどのフロントエンドアプリが、別ドメインのREST APIを呼ぶとき
- Webアプリが外部の天気予報APIや地図APIを直接呼ぶとき
- ローカル開発中に
localhost:3000のフロントがlocalhost:8080のAPIを呼ぶとき(ポートが違うから別オリジン!)
特に3つ目は意外に思われがちだけど、localhost:3000 と localhost:8080 はポート番号が違うから別のオリジン扱いになる。開発中にCORSエラーで詰まる原因でもっとも多いケースのひとつだよ。
CORSはブラウザだけの話
最後に大事なことをひとつ。CORSはあくまでブラウザが行うセキュリティチェックだ。だからcurlコマンドやPostmanなどのツールでAPIを呼ぶときには、CORSエラーは発生しない。「Postmanでは動いたのにブラウザで動かない」という状況になっても、それはおかしくなくて、ブラウザだけが持つ仕組みが働いているからなんだ。CORSを「ブラウザとサーバーの間の約束ごと」と覚えておけば、こういうときに混乱しなくなるよ。
