CORS エラーとは?「has been blocked by CORS policy」の原因と直し方【メッセージ別】

PR
CORS エラーとは?「has been blocked by CORS policy」の原因と直し方【メッセージ別】
この記事は約43分で読めます。

CORS エラーとは、ブラウザー上の JavaScript が別のオリジンの API を呼んだとき、API のサーバーが「このオリジンから読んでよい」という許可を正しく返していないために、ブラウザーが結果を JavaScript に渡さず止めるエラーです。オリジンとは、URL のプロトコル(http や https)・ホスト・ポート番号の組のことです。Chrome のコンソールには、Access to fetch at '…' from origin '…' has been blocked by CORS policy: … という形で出ます。

直し方は、この文の最後の「理由」の部分で決まります。MDN は、ほとんどの CORS エラーは、別のオリジンを許すかどうかを決めるのがサーバーなので、サーバーでしか直せない、と書いています。たとえば No 'Access-Control-Allow-Origin' header is present on the requested resource. と出たときは、サーバーが、許すオリジンを Access-Control-Allow-Origin というレスポンスヘッダー(サーバーが返す応答に付く情報)に入れて返すのが基本の直し方です。

この記事は、ブラウザーから API を呼んで初めてこのエラーに当たった初心者に向けて、CORS の仕組みと、メッセージ別の原因と直し方、Express・NestJS・FastAPI・Amazon S3 での設定を説明します。コンソールの文面は、Chrome 154.0.8037.93(macOS、画面を出さない headless 起動)で出たものです。Firefox の文面は、Firefox のソースコードにある文字列ファイルから写したもので、Firefox では確かめていません。サーバー側の動きは、Express 5.2.1(cors 2.8.6)・NestJS 12.1.2・FastAPI 0.142.2(Starlette 1.7.0)でのものです(どれも 2026年10月3日時点の最新版です)。

この記事でわかること
  • CORS とは何か。エラーを出しているのがサーバーではなくブラウザーであること(curl では起きない理由)
  • 見る場所と、has been blocked by CORS policy のあとに続く「理由」の読み方、メッセージ別の原因と直し方(Chrome 154 の文面。Firefox の Reason つき)
  • プリフライト(OPTIONS)と、資格情報(Cookie など)付きのリクエストで気をつけること
  • Express・NestJS・FastAPI・Amazon S3 で、許すオリジンを設定する書き方
  • no-cors にする、ブラウザーの CORS を切る、といった回避が直し方にならない理由と、代わりにやること
  1. CORS とは?エラーを出しているのは誰?(ブラウザーとサーバーの役割)
    1. オリジンと同一オリジンポリシー
    2. curl や Postman では動くのに、ブラウザーだけエラーになるのはなぜ?
    3. ステータスコードが 200 なのに CORS エラーになるのはなぜ?
  2. 「has been blocked by CORS policy」はどう読む?メッセージ別の原因と直し方
    1. まず見る場所
    2. メッセージの形(Chrome 154)
    3. 理由別の読み方
      1. 許可のヘッダー(Allow-Origin)が無い
      2. 資格情報付きなのに、* が返っている
      3. 返したオリジンが、ページのオリジンと合わない
      4. 返したオリジンが、複数ある
      5. 資格情報を許す印(Allow-Credentials)が無い
      6. プリフライトで止まった
      7. file:// で開いたとき(from origin 'null')
      8. 公開サイトから localhost や社内のネットワークを呼んだ
      9. Firefox だけに出る CORS request did not succeed
  3. プリフライトと資格情報(Cookie など)とは?
    1. プリフライトとは?
    2. 資格情報(Cookie など)付きのリクエスト
  4. サーバー側でどう直す?Express・NestJS・FastAPI・Amazon S3
    1. Express(cors パッケージ)
    2. NestJS
    3. FastAPI(CORSMiddleware)
    4. Amazon S3
  5. やってはいけない回避と、代わりにやること
    1. やってはいけない回避
    2. 代わりにやること
  6. よくある質問
    1. TypeError: Failed to fetch は CORS エラーですか?
    2. Access-Control-Allow-Origin を設定したのに直らない、一部の URL だけエラーになるのはなぜ?
    3. Access-Control-Allow-Origin に複数のオリジンを書ける?
    4. CORS の設定ができたか確かめるには?
  7. まとめ
  8. 参考資料

CORS とは?エラーを出しているのは誰?(ブラウザーとサーバーの役割)

CORS は Cross-Origin Resource Sharing の略で、MDN 日本語版の訳は「オリジン間リソース共有」です。MDN は、サーバーが HTTP ヘッダーを使って「自分以外のどのオリジンからなら、ブラウザーが読み込みを許してよいか」を示す仕組みだと説明しています。MDN の用語集は、オリジンをまたいだリクエスト(ブラウザーからサーバーへの要求)のレスポンス(サーバーからの返事)に、フロントエンドの JavaScript がアクセスするのを、ブラウザーが止めるかどうかを決める仕組み、とも説明しています。

オリジンと同一オリジンポリシー

オリジンは、プロトコル(スキーム)・ホスト・ポートの組です。3つとも同じなら同じオリジン、どれか1つでも違えば別のオリジンです。MDN の比較表(基準は http://store.company.com/dir/page.html)では、次のようになります。

  • パスだけが違う URL は、同じオリジンです
  • https://store.company.com/page.html は、プロトコルが違うので別のオリジンです
  • http://store.company.com:81/dir/page.html は、ポートが違うので別のオリジンです(http:// の既定のポートは 80)
  • ホストが news.company.com の URL も、別のオリジンです

FastAPI の文書も、http://localhost・https://localhost・http://localhost:8080 は、どれも localhost でも、プロトコルかポートが違うので、すべて別のオリジンだと書いています。ページを http://localhost:5501、API を http://localhost:5502 に置いた場合も、ポートが違うので別のオリジンです。この記事の例は、この組み合わせです。

別のオリジンがなぜ問題になるかというと、ブラウザーに同一オリジンポリシーがあるからです。同一オリジンポリシーは、あるオリジンから読み込まれた文書やスクリプトが、別のオリジンのリソースとどうやり取りできるかを制限する、ブラウザーのセキュリティの仕組みです。MDN は、悪意のあるサイトがブラウザーで JavaScript を動かし、ユーザーがログイン中のウェブメールや社内のイントラネットのデータを読んで、攻撃者に送るのを防ぐものだと説明しています。

fetch() と XMLHttpRequest(XHR)もこの同一オリジンポリシーに従います。そのため、別のオリジンの API を呼ぶ JavaScript は、相手のレスポンスに正しい CORS ヘッダーがあるときだけ、レスポンスを使えます。別のオリジンへのリンク・リダイレクト・フォーム送信や、<img>・<script src> での埋め込みはふつう許され、ふつう許されないのは、別のオリジンのリソースの「読み取り」だと、MDN は書いています。CORS は、サーバーがこの同一オリジンポリシーを緩めるための標準です。

curl や Postman では動くのに、ブラウザーだけエラーになるのはなぜ?

CORS を守らせているのは、ブラウザーです。Express の cors パッケージの公式ページは、このパッケージは「レスポンスにヘッダーを付けるだけで、リクエストを止めない」と書いています。ヘッダーを見て、JavaScript にレスポンスを読ませてよいかを決めるのはブラウザーで、curl・Postman・ほかのサーバーなど、ブラウザー以外のクライアントは CORS を無視します。役割を分けると、次のとおりです。

誰が何をする
サーバー許すオリジンなどを、レスポンスのヘッダーで示す
ブラウザーヘッダーを見て、JavaScript にレスポンスを渡してよいか決める。だめなら止めて、コンソールにエラーを出す

Access-Control-Allow-Origin を返さないテスト用の API を、Chrome 154 から fetch() で呼ぶと、CORS エラーで止まります。同じ URL を curl で呼ぶと、次のとおり 200 OK と本文がそのまま返ります。

$ curl -s -i http://localhost:5502/c01-no-acao
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 03 Oct 2026 10:04:41 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{"ok":true}

Express の cors の公式ページは、CORS はアクセス制御ではない、とも書いています。CORS の設定に関係なく、どの HTTP クライアントからでも API は呼べるので、API を守るのは認証と認可です。

ステータスコードが 200 なのに CORS エラーになるのはなぜ?

CORS は、サーバーの処理が成功したかどうかではなく、ブラウザーが JavaScript にレスポンスを読ませてよいかを決める仕組みだからです。Express の cors の公式ページは、よくある誤解として「CORS が許していないオリジンからのリクエストを止める」を挙げ、「違う」と答えています。サーバーはリクエストを受け取って処理し、CORS のヘッダーは、JavaScript がレスポンスを読んでよいかをブラウザーに伝えるものだ、という説明です。

Chrome 154 では、GET や Content-Type: text/plain の POST が CORS エラーで止まったとき、サーバーのログにはそのリクエストが届いていて、200 で処理されていました。ただし、あとで説明する「プリフライト」で止まったときは、本番のリクエスト(PUT や JSON の POST など)はサーバーに送られませんでした。サーバーのログに残ったのは OPTIONS だけでした。

「has been blocked by CORS policy」はどう読む?メッセージ別の原因と直し方

まず見る場所

CORS で失敗しても、セキュリティのため、JavaScript には詳細が渡されません。分かるのは「エラーが起きた」ことだけで、何が悪かったかを知る方法は、ブラウザーのコンソールを見ることだけだと、MDN は書いています。Chrome 154 では、どの CORS エラーでも、JavaScript 側の例外は TypeError: Failed to fetch でした(XHR のときは onerror が呼ばれ、status は 0)。理由は、コンソールに出る has been blocked by CORS policy: … の文にあります。

Chrome の DevTools(開発者ツール)の文書によると、Network パネルでは、CORS で失敗したリクエストの Status の列に CORS error と出ます。Failed to fetch だけでは CORS かどうかは決められません。この点は、下の「よくある質問」で説明します。

メッセージの形(Chrome 154)

Chrome 154 で、Access-Control-Allow-Origin が無いレスポンスを fetch() したときのコンソールの文です。

Access to fetch at 'http://localhost:5502/c01-no-acao' from origin 'http://localhost:5501' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

形は Access to <種類> at '<URL>' from origin '<ページのオリジン>' has been blocked by CORS policy: <理由> です。<種類> には、fetch() なら fetch、XHR なら XMLHttpRequest が入ります。この文は英語のままで、ブラウザーの言語を日本語(--lang=ja)にして起動しても、英語で出ました。

axios はブラウザーでは既定で XHR を使うので(axios の文書)、axios で呼んだときは Access to XMLHttpRequest at … の形になります。fetch と axios の違いは、fetch と axios違いのわかりやすい話【JS初心者向け完全入門】で説明しています。

文面は Chrome の版でも変わります。Chrome のソースコードの版(タグ)では、120・130・131〜135 のタグには、No 'Access-Control-Allow-Origin' header is present on the requested resource. の後ろに、If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. という一文を付ける記述があり、136〜140・145・154 のタグにはありません。Chrome 154 でも、この一文は付きませんでした。古い Q&A(2019 年のものなど)には、この古い形が残っています。この一文が勧める no-cors が直し方にならない理由は、後半の「やってはいけない回避と、代わりにやること」で説明します。

一方、Chrome 154 は、fetch() のとき、ヘッダーの値が合わない・複数ある・不正、といった一部のエラーの末尾に Have the server send the header with a valid value. を付けます。

理由別の読み方

ここからは、理由の部分ごとに、Chrome 154 の文面、Firefox の Reason、原因、直し方を並べます。<…> は、場合によって変わる部分です。Firefox の文面は、Firefox のソースコードの文字列ファイル(security.properties)から写したもので、引用符は ‘ ’ です。MDN は、Firefox の Reason を 15 個並べて、それぞれに解説ページを用意しています(CORS のエラー(MDN 日本語版))。MDN の見出しと Firefox のソースの文字列とで文面が違うものがあるので、違うものは両方を載せます。

許可のヘッダー(Allow-Origin)が無い

Chrome 154: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Firefox: Reason: CORS header ‘Access-Control-Allow-Origin’ missing
原因: レスポンスに Access-Control-Allow-Origin ヘッダーが無い。
直し方: サーバーが、許すオリジンを Access-Control-Allow-Origin に入れて返す。たとえば Access-Control-Allow-Origin: https://example.com です。

MDN は、Access-Control-Allow-Origin: *(* は、どのオリジンでも、を表すワイルドカード)は公開 API だけに使い、非公開の API には * を使わずに、特定のドメインを書くよう注意しています。非公開の API にワイルドカードを使うのは悪い考えだ、という Warning です。

このヘッダーを返さないサーバーが自分の管理外にあるときは、サーバー側では直せません。代わりの手は、後半の「やってはいけない回避と、代わりにやること」で説明します。ヘッダーを付けたつもりなのに同じ文が出るときは、「よくある質問」の「Access-Control-Allow-Origin を設定したのに直らない、一部の URL だけエラーになるのはなぜ?」を見てください。

資格情報付きなのに、* が返っている

Chrome 154: 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'.
Firefox: Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’
原因: 資格情報(Cookie など)付きのリクエスト(credentials: "include" など)なのに、サーバーが Access-Control-Allow-Origin: * を返した。
直し方: サーバーは * ではなく、許すと決めたオリジンの一覧と照らし、入っているときだけ、そのオリジンを返す(来たオリジンを何でもそのまま返さない。後半の「やってはいけない回避」)。資格情報が要らないなら、クライアント側で付けない。

資格情報とは何かと、* を使えない場面は、次の節の「資格情報(Cookie など)付きのリクエスト」で説明します。

返したオリジンが、ページのオリジンと合わない

Chrome 154: The 'Access-Control-Allow-Origin' header has a value 'http://localhost:3000' that is not equal to the supplied origin. Have the server send the header with a valid value.
Firefox: Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘<値>’
原因: サーバーが返したオリジンと、ページのオリジンが一致しない。
直し方: ページのオリジンを返す。オリジンには、https:// か http:// のプロトコルも含める。

この文は、ページが http://localhost:5501 なのに、サーバーが http://localhost:3000 を返したときのものです。末尾に / を付けた http://localhost:5501/ を返しても、不一致になり、同じ文が出ました。仕様(Fetch Standard)の表にも、「シリアライズしたオリジンには末尾のスラッシュが無い」という注があります。

返したオリジンが、複数ある

Chrome 154: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:5501, http://localhost:3000', but only one is allowed. Have the server send the header with a valid value.
Firefox: Reason: Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed
原因: オリジンを複数並べて返した(またはヘッダーが2つある)。
直し方: 許すと決めたオリジンの一覧と照らし、入っているときだけ、そのオリジンを返す(来たオリジンを何でもそのまま返さない。後半の「やってはいけない回避」)。複数のサイトを許したいときの書き方は、「よくある質問」で説明します。

資格情報を許す印(Allow-Credentials)が無い

Chrome 154: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
Firefox: Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’
原因: 資格情報付きのリクエストなのに、レスポンスに Access-Control-Allow-Credentials: true が無い。
直し方: サーバーが Access-Control-Allow-Credentials: true を返す。資格情報が要らないなら、クライアント側で付けない。true は小文字で書く(True は不可)。

プリフライトで止まった

プリフライト(本番のリクエストの前に、OPTIONS で先に確かめるリクエスト。次の節で説明します)への応答が原因のときは、次のような文が出ます。

  • Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. ── プリフライトへの応答に Access-Control-Allow-Origin が無い。Chrome 154 では、OPTIONS に 404 を CORS ヘッダー無しで返したとき、404 というステータスよりも先にこの文が出ました
  • Response to preflight request doesn't pass access control check: It does not have HTTP ok status. ── プリフライトへの応答が 2xx ではない。404 に Access-Control-Allow-Origin は付けた場合に、この文が出ました。仕様では、プリフライトの成功は ok status(200〜299)に限られます
  • Method PUT is not allowed by Access-Control-Allow-Methods in preflight response. ── 応答の Access-Control-Allow-Methods に、そのメソッドが無い。Firefox は Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’。サーバーがそのメソッドを許すか、許されたメソッドだけを使います
  • Request header field x-custom-header is not allowed by Access-Control-Allow-Headers in preflight response. ── リクエストに付けたヘッダーが Access-Control-Allow-Headers に無い(ヘッダー名は小文字で出ました)。Firefox のソースの文字列は Reason: header ‘<名前>’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response、MDN の見出しは Reason: missing token 'xyz' in CORS header 'Access-Control-Allow-Headers' from CORS preflight channel です。サーバーがそのヘッダーを許すか、そのヘッダーを使いません
  • Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request. ── プリフライトにリダイレクト(3xx)を返した
  • Firefox の Reason: CORS preflight response did not succeed(末尾に Status code: <番号>. が付く)は、プリフライトが成功しなかったときの文です。MDN の見出しは CORS preflight channel did not succeed で、文面が違います

どれも、プリフライトの OPTIONS に、2xx のステータスと CORS のヘッダーで答えることが直し方の中心です。答え方の例は、次の節にあります。

file:// で開いたとき(from origin 'null')

Chrome 154: Cross origin requests are only supported for protocol schemes: chrome, chrome-experimental-site-token-provider, chrome-extension, chrome-untrusted, data, http, https, isolated-app.(並ぶスキームは Chrome 154 のものです)
Firefox: Reason: CORS request not http
原因: HTML ファイルを file:// で開き、ローカルのファイルを fetch や XHR で読んだ。
直し方: ローカルのサーバーを立てて、http://localhost で開く。

MDN は、Firefox や Chrome を含む多くのブラウザーが、ローカルのファイルをすべて不透明なオリジンとして扱うので(既定)、ローカルでテストするなら、ローカルのサーバーを立てるべきだと書いています。

file:// で開いたページから http://localhost の API を呼ぶと、Chrome のメッセージは from origin 'null' になり、サーバーには Origin: null が届きました。MDN は、Access-Control-Allow-Origin: null を使わないよう書いています。file: や data:、サンドボックスの文書のオリジンは null になり、どのオリジンからでも null のオリジンの文書を作れてしまうためです。

公開サイトから localhost や社内のネットワークを呼んだ

Access to fetch at 'http://localhost:5502/lna-test' from origin 'https://example.com' has been blocked by CORS policy: Permission was denied for this request to access the `loopback` address space.

Chrome の Local Network Access(ローカルネットワークへのアクセス)の文です。公開のサイトのページから、localhost(自分のマシン)や社内のネットワークへリクエストを送るとき、サイトがユーザーの許可を得ている必要があります。許可のプロンプト(許可を求める表示)は Chrome 142 から出ます。許可を求められるのは、HTTPS などのセキュアコンテキストのページだけです。文中の loopback は、local と出ることもあります。Chrome 154 の headless ではプロンプトを出せず、許可されなかった結果として、リクエストはサーバーに届きませんでした。画面のある Chrome でプロンプトがどう出るかは、確かめていません。

Firefox だけに出る CORS request did not succeed

Firefox: Reason: CORS request did not succeed。MDN によると、CORS そのものではなく、ネットワークなどの基本的な失敗です。多くは、広告ブロッカーなどのブラウザー拡張機能がリクエストを止めています。ほかに、証明書の不備、HTTPS のページから HTTP を呼んだ(混在コンテンツ)、サーバーが応答しない、なども原因になります。

プリフライトと資格情報(Cookie など)とは?

プリフライトとは?

プリフライトは、本番のリクエストの前に、ブラウザーが OPTIONS というメソッドで、相手のサーバーに「この本番のリクエストを送ってよいか」を確かめるリクエストです。送りたいメソッドを Access-Control-Request-Method に、付けたいヘッダーがあれば Access-Control-Request-Headers に書いて送ります。プリフライトが失敗すると、本番のリクエストは送られません。

プリフライトが起きないのは、MDN が「単純リクエスト」と呼ぶ、次の条件を全部満たすリクエストです。

  • メソッドが GET・HEAD・POST のどれか
  • 手で付けるヘッダーが、Accept・Accept-Language・Content-Language・Content-Type・Range(単一の範囲だけ)のどれか
  • Content-Type が application/x-www-form-urlencoded・multipart/form-data・text/plain のどれか
  • XHR の upload にイベントリスナーが無く、ReadableStream も使っていない

「単純リクエスト」は古い CORS 仕様の言葉で、いまの仕様(Fetch Standard)は使っていない、とも MDN は書いています。

だから、JSON を Content-Type: application/json で POST すると、プリフライトが起きます。PUT や DELETE、独自のヘッダー、Authorization ヘッダーでも起きます。Chrome 154 では、application/json の POST の前に OPTIONS(Access-Control-Request-Method: POST、Access-Control-Request-Headers: content-type)が届き、そのあとに POST が届きました。Content-Type: text/plain の POST には、OPTIONS が届きませんでした。

プリフライトへの応答の例です(MDN)。2xx のステータス(この例では 204 No Content)と、許すオリジン・メソッド・ヘッダーを返します。

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Access-Control-Max-Age は、プリフライトの結果をキャッシュしてよい秒数です。既定は 5 秒で、上限は Firefox が 24 時間(86400 秒)、Chromium が v76 から 2 時間(7200 秒)です。

資格情報(Cookie など)付きのリクエスト

資格情報(credentials)は、Cookie や HTTP 認証のことです(MDN 日本語版の訳語に合わせています)。ブラウザーは、オリジンをまたぐ fetch() と XHR では、既定では資格情報を送りません。送るには、fetch() は credentials: "include"、XHR は withCredentials = true にします。資格情報付きのときは、サーバーに次のルールが加わります。

  • レスポンスに Access-Control-Allow-Credentials: true が要る。無いと、単純な GET でも、ブラウザーはレスポンスを捨てて、JavaScript に渡しません。true は小文字で書きます
  • Access-Control-Allow-Origin・Access-Control-Allow-Headers・Access-Control-Allow-Methods・Access-Control-Expose-Headers のどれにも * を使えない。Access-Control-Allow-Origin: https://example.com のように、明示的に書きます
  • プリフライトには資格情報が付かない。資格情報付きの本番のリクエストを許すなら、プリフライトへの応答にも Access-Control-Allow-Credentials: true が要ります
  • Authorization ヘッダーは、Access-Control-Allow-Headers に名前で書きます

Fetch Standard の表(CORS protocol and credentials)を、次のようにまとめます。

リクエストの資格情報Access-Control-Allow-OriginAccess-Control-Allow-Credentialsレスポンスの共有
なし*(なし)共有される
なし*true共有される(資格情報なしなら無視される)
あり*true共有されない
ありページのオリジンそのものtrue共有される
ありページのオリジンそのものTrue共有されない

仕様は、資格情報付きのレスポンスを共有したり、資格情報付きのリクエストを許したりするのは、一般にかなり危険で、十分な注意が要る、と書いています。また MDN は、別のドメインへ資格情報付きで送るときも、サードパーティ Cookie の方針は、サーバーとクライアントの設定に関係なく、そのまま適用されると書いています。

サーバー側でどう直す?Express・NestJS・FastAPI・Amazon S3

どのサーバーでも、やることは、許すオリジンを決めて、レスポンス(プリフライトへの応答を含む)に CORS のヘッダーを付けることです。Express・NestJS・FastAPI・Amazon S3 の書き方を、それぞれの公式の文書に沿って説明します。

Express(cors パッケージ)

Express では、cors パッケージを使います。npm install cors で入れます。cors は型定義を同梱していないので、TypeScript なら npm install --save-dev @types/cors も入れます。

全部のオリジンに許すなら、app.use(cors()); の1行で、Access-Control-Allow-Origin: * が付きます。オリジンを決めるときは、origin を渡します。次は公式ページの例から、末尾の app.listen を省いたものです。

var express = require('express');
var cors = require('cors');
var app = express();

var corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
};

// Adds headers: Access-Control-Allow-Origin: http://example.com, Vary: Origin
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({ msg: 'Hello' });
});

出典: cors(Express 公式)

  • origin には、文字列(そのオリジンだけ)のほか、true(リクエストのオリジンをそのまま返す)、false(CORS を無効にする)、"*"(全部)、正規表現、配列(文字列と正規表現を混ぜられる)、関数を渡せます
  • app.use(cors()) のようにアプリ全体に付けると、プリフライトも全部のルートで処理されます。Express 5.2.1(cors 2.8.6)では、プリフライトに 204 と Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE が返りました
  • 資格情報付きの通信を許すときは、credentials: true を渡します。cors({ origin: 'http://localhost:5501', credentials: true }) では、Access-Control-Allow-Origin: http://localhost:5501・Vary: Origin・Access-Control-Allow-Credentials: true が返りました。cors({ origin: '*', credentials: true }) と書いても、パッケージは止めず、* と true が両方出ます。資格情報付きのリクエストでは、ブラウザーが、前の節の must not be the wildcard '*' の文で拒否します
  • ミドルウェアは、読み込んだ順に動きます。app.use(cors()) より前に登録したルートには、CORS ヘッダーが付きませんでした。cors() より後のルートでは、例外の 500 や 404 にも Access-Control-Allow-Origin: * が付きました

Express 5 では、注意がひとつあります。cors の公式ページにある app.options('*', cors()) は、Express 5.2.1 ではそのままだと動かず、起動時に PathError: Missing parameter name at index 1: *; visit https://git.new/pathToRegexpError for info になります。Express 5 では、ワイルドカードに名前が要ります(/*splat、ルートも含めるなら /{*splat})。app.use(cors()) を使うなら、この行は要りません。

NestJS

NestJS は、内部で、Express なら cors パッケージ、Fastify なら @fastify/cors を使います。オプションは、それらと同じです。有効にするには、enableCors() を呼ぶか、NestFactory.create に cors: true を渡します(どちらにも設定のオブジェクトを渡せます)。NestJS の始め方は、Nest.jsを使ったリクエストパラメータの取得方法【超入門】で説明しています。

const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(process.env.PORT ?? 3000);
const app = await NestFactory.create(AppModule, { cors: true });
await app.listen(process.env.PORT ?? 3000);

出典: NestJS(CORS)

既定の Access-Control-Allow-Methods は、Express 版が GET,HEAD,PUT,PATCH,POST,DELETE、Fastify 版は GET,HEAD,POST だけです。Fastify では、別のオリジンからの PUT・PATCH・DELETE が、プリフライトで拒否されるので、methods を書きます(Fastify 版は確かめていません)。

app.enableCors({
  methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
});

NestJS 12.1.2(Express 版)では、app.enableCors() でも { cors: true } でも、GET に Access-Control-Allow-Origin: * が付き、プリフライトに 204 と Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE が返りました。CORS を有効にしないと、プリフライトの OPTIONS には 404 が返りました(前の節の「プリフライトで止まった」の形です)。

FastAPI(CORSMiddleware)

FastAPI では、CORSMiddleware を使います(from fastapi.middleware.cors import CORSMiddleware)。中身は Starlette のもので、from starlette.middleware.cors import CORSMiddleware でも使えます。FastAPI のアプリの作り方は、【Python入門】Fast APIの実装手順とフレームワーク技術選定で説明しています。公式の例は次のとおりです。

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

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]

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


@app.get("/")
async def main():
    return {"message": "Hello World"}

出典: FastAPI(CORS)

引数の既定値は、allow_methods が ['GET']、allow_headers が []、allow_credentials が False、expose_headers が []、max_age が 600 秒です。既定は制限が厳しいので、必要なものを明示的に許します。許すオリジンは、allow_origins(['*'] で全部)か allow_origin_regex で指定します。

許すオリジンは、明示してください。 FastAPI と Starlette の文書は、allow_credentials=True のとき、allow_origins・allow_methods・allow_headers のどれも ['*'] にできず、すべて明示する、と書いています。ワイルドカード("*")では、Cookie や、Bearer トークンで使う Authorization ヘッダーなど、資格情報を伴う通信が除かれるので、許すオリジンを明示するほうがよい、とも書いています。

版を添えた注意です。 Starlette 1.7.0(FastAPI 0.142.2)では、allow_origins=["*"] と allow_credentials=True を一緒に書くと、エラーにならずに起動し、来たオリジンをそのまま Access-Control-Allow-Origin に入れて、Access-Control-Allow-Credentials: true を返しました(http://evil.example からのリクエストでも同じでした)。どのサイトからも資格情報つきで呼べてしまう動きで、文書の文とも違います。この組み合わせは書かず、allow_origins に許すオリジンを書いてください。なお、上の公式の例は、allow_credentials=True のまま allow_methods=["*"] と allow_headers=["*"] を使っていて、この点も文書の文と食い違っています。Starlette 1.7.0 は、メソッドの * を具体的な一覧(DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, QUERY)に、ヘッダーの * を要求されたヘッダーに置き換えて返しました。

  • プリフライト(Origin と Access-Control-Request-Method が付いた OPTIONS)には、ミドルウェアが CORS ヘッダー付きで 200 か 400 を返します。allow_origins だけを書いた場合(ほかの引数は既定)、PUT のプリフライトには 400 Disallowed CORS method、許していないヘッダーには 400 Disallowed CORS headers、許していないオリジンには 400 Disallowed CORS origin が返りました
  • 許していないオリジンからの GET にも、200 と本文が返り、Access-Control-Allow-Origin は付きません。ブラウザーでは、ヘッダーが無いときの CORS エラーになり、サーバー側のステータスは 200 のままです
  • app.add_middleware(CORSMiddleware, …) の形では、ルートで処理されない例外の 500 のレスポンスに、CORS ヘッダーが付きませんでした。ブラウザーでは CORS エラーに見えますが、原因はサーバーの例外です(HTTPException(404) には付きました)。Starlette の文書は、例外のときのエラー応答にも CORS ヘッダーを付けたいなら、アプリ全体を CORSMiddleware で包むことを勧めています

Amazon S3

S3 の CORS 設定は、許すオリジンやメソッドなどのルールを書いた、JSON の文書です。ルールは 100 個まで書けます。要素は次のとおりです。

  • AllowedMethods: GET・PUT・POST・DELETE・HEAD
  • AllowedOrigins: * を1つまで含められる(例 http://*.example.com)。* だけなら全オリジン
  • AllowedHeaders: プリフライトの Access-Control-Request-Headers の各ヘッダーと照合する。* を1つまで含められる(例 x-amz-*)
  • ExposeHeaders: JavaScript から読めるようにするレスポンスヘッダー
  • MaxAgeSeconds: プリフライトの応答をキャッシュする秒数

AWS の文書にある設定の例(2つ目)です。

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://www.example.com"
        ],
        "ExposeHeaders": [
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2"
        ],
        "MaxAgeSeconds": 3000
    }
]

出典: Amazon S3 ユーザーガイド(CORS の設定)

S3 は、ブラウザーからプリフライトを受けると、バケットの CORS 設定のうち、最初に一致した CORSRule を使います。一致するには、Origin が AllowedOrigins に、Access-Control-Request-Method が AllowedMethods に、Access-Control-Request-Headers が AllowedHeaders に合う必要があります。CORS を有効にしても、ACL とポリシーはそのまま効きます。

AWS の文書の表記では、S3 のコンソールで、左の [汎用バケット](General purpose buckets)→ バケット名 → [Permissions](アクセス許可)→ [CORS (クロスオリジンリソース共有)] の [編集] を選び、エディターに JSON を貼って、[Save changes](変更を保存)します(コンソールの画面は確かめていません)。

エラーは、CORS を設定していないと 403 Forbidden(CORS Response: CORS is not enabled for this bucket.)、ルールが合わないと 403 Forbidden(CORS Response: This CORS request is not allowed.)です。後者は、オリジン・メソッド・ヘッダーのどれかが許されていません。CloudFront などのプロキシを通すときは、OPTIONS を許可し、Origin・Access-Control-Request-Headers・Access-Control-Request-Method を転送し、Origin をキャッシュキーに入れます。オブジェクトの独自メタデータ(x-amz-meta-…)のような独自のヘッダーを JavaScript で読むには、ExposeHeaders に書きます(ブラウザーは既定で独自ヘッダーを見せません)。署名つき URL での CORS エラーは、署名つきURL(Presigned URL)でのCORSエラー【簡潔に解決】でも説明しています。

やってはいけない回避と、代わりにやること

MDN は、ほとんどの CORS エラーは、許すかどうかを決めるのがサーバーなので、サーバーでしか直せない、と書いています。クライアント側でできることとして MDN が挙げるのは、プリフライトを避ける、no-cors、プロキシの3つです。サーバーを直せないときに手を出しやすい回避を、順に見ます。

やってはいけない回避

  • mode: "no-cors" でエラーを消す。 no-cors にすると CORS のチェックは外れますが、レスポンスは opaque(不透明)になり、ステータスは 0、ヘッダーは空、本文は JavaScript から読めません。Chrome 154 でも ok status=0 type=opaque でした。中身を読まなくてよいとき(アクセス解析のビーコンなど)向けで、中身が要る API 呼び出しの直し方にはなりません。メッセージ別の節で触れた、古い Chrome の文面が勧めていた no-cors は、このことです
  • ブラウザー側で CORS や同一オリジンポリシーを切る。 Chromium のソースのコメントは、起動オプション --disable-web-security を「同一オリジンポリシーを守らせない。ウェブサイトのテスト専用」と説明しています(--user-data-dir が無いと効きません)。同一オリジンポリシーは、ログイン中のウェブメールや社内のデータを悪いサイトに読まれるのを防ぐ仕組みなので、ブラウザー側で切ると、その防御が外れます。拡張機能を使っても、ブラウザー側で切れば同じことです。Firefox の設定 content.cors.disable を true にすると、CORS のリクエストは必ず Reason: CORS disabled で失敗し、MDN は「ユーザーが CORS を元に戻す必要がある」と書いています
  • Access-Control-Allow-Origin: * を何にでも付ける。 * は公開 API だけに使い、非公開の API には特定のドメインを書きます(メッセージ別の節で紹介した MDN の Warning)
  • リクエストのオリジンをそのまま返す。 Express の origin: true は、リクエストのオリジンをそのまま返します。FastAPI の節で書いた、Starlette 1.7.0 の allow_origins=["*"] と allow_credentials=True の動きも同じです。MDN の言う「* を使わずにどのサイトでも許す」と同じ状態で、資格情報と組み合わせるなら、資格情報の節で紹介した、仕様の注意(かなり危険)が当てはまります
  • allow_origin_regex に .* や .+ を使う。 Starlette の文書は、/・@・#・? といった URL の特殊文字にも一致して、許しすぎることがあるので使わず、[a-zA-Z0-9-]+ のような文字の集合を使うよう書いています
  • Vite の server.cors を true にする。 どのサイトからでも開発サーバーに要求を送れて、ソースコードを持っていかれうる、と Vite の文書は警告し、許すオリジンを明示したリストを常に使うことを推奨しています(既定で許すのは、localhost・127.0.0.1・::1 だけです)

代わりにやること

まずは、サーバー側の節のように、サーバーを直します。相手のサーバーを管理できないときの手は、次のとおりです。

  • 自分が管理するサーバー(プロキシ)を通す。 プロキシが代わりに取ってきて、適切な CORS ヘッダーを付けて返します。MDN は、遅延が増え、プロキシに頼ることになるが、ほかの手が使えないときに役立つ、と書いています
  • 開発中は、Vite の server.proxy を使う。 パスがキーで始まるリクエストを、開発サーバーが指定した先へ転送します(target に転送先、changeOrigin: true、rewrite でパスの書き換え)。開発サーバーの設定なので、本番には効きません。Vite は公式の文書で確かめた内容で、動かしてはいません
  • プリフライトが起きない形に組み直す。 単純なリクエストには Access-Control-Allow-Origin を返し、プリフライトにだけ答えないサーバーの場合に限ります(プリフライトの節の「単純リクエスト」の条件)。Access-Control-Allow-Origin をまったく返さないサーバーでは、単純なリクエストにしても中身は読めません

よくある質問

TypeError: Failed to fetch は CORS エラーですか?

CORS のエラーのこともありますが、Failed to fetch だけでは決められません。Chrome 154 では、どの CORS エラーでも JavaScript 側の例外は TypeError: Failed to fetch でしたが、サーバーが止まっていて接続できないときも、同じ TypeError: Failed to fetch になりました。このときコンソールには、CORS の文ではなく net::ERR_CONNECTION_REFUSED が出ました。

仕様(Fetch Standard)では、CORS のチェックに失敗するとネットワークエラーになり、fetch() はネットワークエラーのとき TypeError で失敗します。JavaScript には詳細が渡されないので、原因は、コンソールの文で見分けます。

Access-Control-Allow-Origin を設定したのに直らない、一部の URL だけエラーになるのはなぜ?

設定したつもりでも、ヘッダーが付いていない応答が混ざっていないかを確かめます。確かめられた原因は、次のとおりです。

  • ミドルウェアの順番。 Express では、app.use(cors()) より前に登録したルートには、CORS ヘッダーが付きません
  • リダイレクト。 Chrome 154 では、301・302 の応答そのものに Access-Control-Allow-Origin が無いと、その時点で No 'Access-Control-Allow-Origin' header is present… の文で止まります(文中の URL は、リダイレクト前のものです)。スラッシュ無しの URL からスラッシュ付きの URL への 301 で止まり、301 にもヘッダーを付けたら通りました。別のオリジンへのリダイレクトの後で止まると、Chrome は Access to fetch at '<最後の URL>' (redirected from '<最初の URL>') from origin … の形で、両方の URL を出します
  • エラーの応答。 FastAPI の例外の 500 には、ヘッダーが付かないことがあります(FastAPI の節)。Express では、cors() より後のルートなら、500 や 404 にも付きます
  • プリフライトへの応答。 OPTIONS が 2xx ではない、またはヘッダーが無いと、プリフライトの文で止まります(メッセージ別の節)
  • Apache・nginx の書き方。 Apache の Header set Access-Control-Allow-Origin 'https://example.com' は、サーバー自身が作る 2xx 以外の応答(リダイレクトなど)には付きません。エラーにも付けるなら Header always set … にします。nginx の add_header 'Access-Control-Allow-Origin' 'https://example.com' always; の always は、応答コードに関係なく付けるための指定です。always が無いと、200・201・204・206・301・302・303・304・307・308 のときだけ付きます

Access-Control-Allow-Origin に複数のオリジンを書ける?

書けません。MDN は、ブラウザーが受け付ける値は、1つのオリジンか null のどちらかで、オリジンの一覧は返せないと書いています(返すと、Chrome 154 では前の contains multiple values … but only one is allowed. の文になります)。

複数のオリジンから使いたいときは、サーバーが、許すと決めたオリジンの一覧と照らし、入っているときだけ、そのオリジン1つを返します(来たオリジンを何でもそのまま返すのは「やってはいけない回避」です)。Express の origin には配列を渡せますし、FastAPI の allow_origins に許すオリジンを1つずつ書くと、一致したオリジン1つと Vary: Origin が返ります。リクエストに応じて値を変えるときは、Vary: Origin も返します。

CORS の設定ができたか確かめるには?

ブラウザーが送るプリフライトを、curl で再現できます。Origin と Access-Control-Request-Method を付けた OPTIONS を送り、返ってきたヘッダーを見ます。次は例で、Origin は呼び出すページのオリジン、URL は確かめたい API の URL に置き換えます。

curl -s -i -X OPTIONS -H "Origin: http://localhost:5501" -H "Access-Control-Request-Method: PUT" http://localhost:5502/c10-put

GET, POST だけを許すテスト用の API では、次のように返りました。

HTTP/1.1 204 No Content
Content-Type: application/json
Access-Control-Allow-Origin: http://localhost:5501
Access-Control-Allow-Methods: GET, POST
Date: Sat, 03 Oct 2026 10:04:41 GMT
Connection: keep-alive
Keep-Alive: timeout=5

この API に PUT を送るブラウザーは、Chrome 154 で Method PUT is not allowed by Access-Control-Allow-Methods in preflight response. を出します。curl は CORS を確かめないので、「PUT が許されていない」と判断するのはブラウザーです。ヘッダーが付いているかは curl で、通るかどうかはブラウザーのコンソールで確かめます。

Amazon S3 の文書も、curl -v -X OPTIONS に Origin と Access-Control-Request-Method(必要なら Access-Control-Request-Headers)を付けて、プリフライトを再現する方法を紹介しています。許されていないヘッダーが1つでもあると、CORS のレスポンスヘッダーは1つも返りません。

まとめ

  • CORS エラーは、サーバーが「このオリジンから読んでよい」という許可(CORS ヘッダー)を正しく返していないとき、ブラウザーが、JavaScript にレスポンスを渡さず止めるエラー。止めているのはブラウザーなので、curl や Postman では起きず、サーバー側のステータスが 200 でも起きる
  • 見る場所は、ブラウザーのコンソール。has been blocked by CORS policy: の後ろの「理由」で原因が分かる。TypeError: Failed to fetch だけでは、CORS かどうか決められない
  • ほとんどの CORS エラーは、サーバーでしか直せない。基本は、許すオリジンを Access-Control-Allow-Origin に入れて返すこと。複数のオリジンを許すときは、許すと決めた一覧と照らして、入っているオリジン1つだけを返す(来たオリジンを何でもそのまま返さない)
  • application/json の POST・PUT・DELETE・独自のヘッダー・Authorization ヘッダーでは、プリフライトが起きる。OPTIONS に、2xx のステータスと CORS ヘッダーで答える。プリフライトが失敗すると、本番のリクエストは送られない
  • 資格情報(Cookie など)付きのリクエストでは、* を使えず、Access-Control-Allow-Credentials: true が要る
  • Express は cors、NestJS は enableCors()、FastAPI は CORSMiddleware、S3 はバケットの CORS 設定。Starlette 1.7.0 では、allow_origins=["*"] と allow_credentials=True を一緒に書くと、来たオリジンをそのまま返すので、書かない
  • no-cors にする、ブラウザーの CORS を切る、といった回避は、直し方にならない。サーバーを管理できないときは、自分が管理するプロキシを通す

参考資料

この記事の Chrome の文面は、Chrome 154.0.8037.93 で出たものです。同じ版のソースコードの文字列ファイルとも一致します。Firefox の文面は、Firefox のソースコードの文字列ファイルから写したものです。

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月3日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

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