CORSエラーは、ブラウザが「別のオリジン(ドメイン・ポート・プロトコルの組み合わせ)のAPIから返ってきたレスポンスを、許可がないためJavaScriptに渡さない」ときに起きるエラーです。解決するには、APIを提供するサーバー側で「このオリジンからのアクセスを許可する」というヘッダーを返すように設定します。

フロントエンドのコードをいくら変えても、基本的にCORSエラーは解決しません。この記事では、CORSの仕組みを最小限おさえたうえで、Chromeに表示されるエラーメッセージ別の原因と対処、主要なフレームワークとnginxの設定例、開発時のプロキシ、安全な設定の注意点までを解説します。

CORSエラーとは:なぜブラウザが止めるのか

ブラウザには、同一オリジンポリシーという安全のためのルールがあります。あるWebページのJavaScriptは、原則として同じオリジンのサーバーからのレスポンスしか読み取れません。

オリジンは、「プロトコル(スキーム)・ホスト・ポート」の3つの組み合わせです。

ページのURL APIのURL 同じオリジンか 理由
https://example.com https://example.com/api 同じ 3つとも一致
https://example.com https://api.example.com 違う ホストが違う
http://localhost:5173 http://localhost:8080 違う ポートが違う
http://localhost:3000 http://127.0.0.1:3000 違う ホストの表記が違う
http://example.com https://example.com 違う プロトコルが違う

このルールがあることで、悪意のあるサイトのJavaScriptが、ログイン中のあなたの権限で別のサイトのデータを読み取ることを防いでいます。

CORS(Cross-Origin Resource Sharing)は、このルールをサーバー側が明示的に緩める仕組みです。サーバーが「このオリジンからなら読んでよい」とヘッダーで伝えると、ブラウザはレスポンスをJavaScriptに渡します。

curlやPostmanでは成功する理由

CORSはブラウザが行うチェックです。curlやPostman、サーバー同士の通信では、CORSのチェックは行われません。「curlでは動くのに、ブラウザだけエラーになる」のは、CORSの典型的な症状です。

また、多くの場合リクエスト自体はサーバーに届いており、サーバーはレスポンスを返しています。ブラウザが、そのレスポンスをJavaScriptに渡すのを止めているのです。

単純リクエストとプリフライトリクエスト

CORSには、2種類の流れがあります。どちらになるかで、必要な設定が変わります。

単純リクエスト

次の条件をすべて満たすリクエストは、そのまま送信されます。

  • メソッドが GET・HEAD・POST のいずれか
  • 独自のヘッダー(Authorizationなど)を付けていない
  • Content-Typeが application/x-www-form-urlencoded・multipart/form-data・text/plain のいずれか

サーバーのレスポンスに Access-Control-Allow-Origin があれば、ブラウザはJavaScriptに結果を渡します。

プリフライトリクエスト

条件を1つでも満たさない場合、ブラウザは本番のリクエストの前に、OPTIONSメソッドで「このリクエストを送ってよいか」を問い合わせます。これがプリフライトリクエストです。

OPTIONS /api/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600

JSONを送るPOST(Content-Type: application/json)や、Authorizationヘッダーを付けたリクエストは、プリフライトになります。現代のWebアプリのAPI呼び出しは、ほとんどがこちらです。

CORSのヘッダー一覧

ヘッダー 意味 例
Access-Control-Allow-Origin アクセスを許可するオリジン https://app.example.com
Access-Control-Allow-Methods 許可するメソッド(プリフライト) GET, POST, PUT, DELETE
Access-Control-Allow-Headers 許可するリクエストヘッダー(プリフライト) Authorization, Content-Type
Access-Control-Allow-Credentials Cookieなどの認証情報を含むリクエストを許可する true
Access-Control-Max-Age プリフライトの結果をキャッシュする秒数 600
Access-Control-Expose-Headers JavaScriptから読めるようにするレスポンスヘッダー X-Total-Count
Vary オリジンによってレスポンスが変わることをキャッシュに伝える Origin

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CORSエラーの原因と解決方法|エラーメッセージ別に解説」

エラーメッセージ別の原因と対処

Chromeの開発者ツールのコンソールに表示されるメッセージから、原因を特定できます。

No 'Access-Control-Allow-Origin' header is present on the requested resource.

Access to fetch at 'https://api.example.com/orders' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

原因:レスポンスに Access-Control-Allow-Origin ヘッダーがない。

対処:APIサーバーで、フロントエンドのオリジンを許可する設定を追加します。設定したはずなのに出る場合は、次の2点を確認します。

  • エラーレスポンス(401・404・500など)にもヘッダーが付いているか。多くのサーバーやフレームワークは、エラー時にCORSのヘッダーを付けません。その結果、本当の原因(認証エラーやサーバーエラー)が、CORSエラーとして見えてしまいます。開発者ツールの「Network」タブで、実際のステータスコードを確認してください
  • URLが間違っていないか。存在しないURLへのリクエストが404になり、上記の理由でCORSエラーに見えることがあります

Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

原因:プリフライト(OPTIONSリクエスト)に対して、サーバーが 200番台以外(401・403・404・405など)を返している。

対処:よくある原因は次のとおりです。

  • 認証のチェックが、OPTIONSリクエストにもかかっている。プリフライトにはAuthorizationヘッダーやCookieが付かないため、認証が必要な設定だと401になります。OPTIONSは認証の対象外にします
  • OPTIONSメソッドのルートがない(404・405になる)
  • WAFやAPIゲートウェイが、OPTIONSを拒否している

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'.

原因:Cookieなどの認証情報を送る設定(credentials: 'include' や withCredentials: true)で、サーバーが Access-Control-Allow-Origin: * を返している。

対処:認証情報を送る場合は、ワイルドカード(*)は使えません。許可するオリジンを具体的に指定し、あわせて Access-Control-Allow-Credentials: true を返します。

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'.

原因:認証情報を送る設定なのに、サーバーが Access-Control-Allow-Credentials: true を返していない。

対処:サーバーで Access-Control-Allow-Credentials: true を返します。

Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

原因:リクエストに付けたヘッダー(この例ではAuthorization)が、プリフライトのレスポンスで許可されていない。

対処:Access-Control-Allow-Headers に、そのヘッダー名を追加します。

Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

原因:使ったメソッドが、プリフライトのレスポンスで許可されていない。

対処:Access-Control-Allow-Methods に、そのメソッドを追加します。

The 'Access-Control-Allow-Origin' header contains multiple values ', ', but only one is allowed.

原因:ヘッダーが2回付いている。アプリケーションとnginx(またはAPIゲートウェイ)の両方でCORSの設定をしている場合によく起きます。

対処:CORSのヘッダーを付ける場所を1か所に統一します。

The 'Access-Control-Allow-Origin' header has a value 'https://a.example.com' that is not equal to the supplied origin.

原因:許可しているオリジンと、実際のオリジンが違う。http と https、www の有無、ポート番号、末尾のスラッシュの違いがよくある原因です。

対処:開発者ツールで、リクエストの Origin ヘッダーの値を確認し、完全に一致する値を許可します(末尾のスラッシュは付けません)。

Redirect is not allowed for a preflight request.

原因:プリフライトのリクエストが、リダイレクト(301・302など)されている。httpからhttpsへの転送や、末尾のスラッシュを付ける転送でよく起きます。

対処:フロントエンドから、リダイレクトされない最終的なURLを直接呼び出します。

サーバー側の設定例

どの例も、https://app.example.com からのアクセスを、Cookie付きで許可する設定です。

Express(Node.js)

import cors from "cors";

app.use(cors({
  origin: ["https://app.example.com"],
  credentials: true,
}));

Spring Boot

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

Spring Securityを使っている場合は、セキュリティの設定でもCORSを有効にする必要があります(有効にしないと、プリフライトが認証で拒否されます)。

FastAPI(Python)

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

nginx

location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
        add_header Access-Control-Allow-Credentials "true" always;
        add_header Access-Control-Max-Age 600 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Vary Origin always;
    proxy_pass http://backend;
}

nginxの add_header は、既定では成功時のレスポンスにしか付きません。always を付けると、エラーのレスポンスにもヘッダーが付き、本当のエラー内容をフロントエンドで確認できるようになります。

開発環境では、プロキシで同じオリジンにする

開発中は、フロントエンド(例:localhost:5173)とAPI(例:localhost:8080)のポートが違うため、CORSエラーが起きがちです。開発サーバーのプロキシ機能を使うと、ブラウザからは同じオリジンに見えるため、CORSの設定なしで開発できます。

// vite.config.js
export default {
  server: {
    proxy: {
      "/api": { target: "http://localhost:8080", changeOrigin: true },
    },
  },
};

フロントエンドからは fetch("/api/orders") のように、オリジンを付けずに呼び出します。本番環境でも、フロントエンドとAPIを同じドメインの配下に置く(リバースプロキシで振り分ける)構成にすれば、CORS自体が不要になります。

やってはいけない「解決方法」

方法 問題点
fetch に mode: "no-cors" を付ける エラーは消えるが、レスポンスの中身が読めなくなる(空のレスポンスになる)
ブラウザのセキュリティを無効にして起動する・拡張機能で回避する 自分の環境でしか動かない。利用者のブラウザでは同じエラーが出る
受け取ったOriginを、そのまま許可する 認証情報を許可している場合、どのサイトからでもログイン中のデータを読める脆弱性になる
誰でも使える外部のCORSプロキシを経由する 通信内容やトークンが、第三者のサーバーを経由する

安全な設定のポイント

  • 許可するオリジンは、一覧で具体的に指定する。オリジンを動的に判定する場合は、許可リストと照合してから返す
  • オリジンによって返す値を変える場合は、Vary: Origin を付ける。付けないと、CDNなどのキャッシュが別のオリジン向けのレスポンスを返すことがある
  • * を使ってよいのは、認証なしで誰でも使える公開APIに限る

CORSはCSRF対策ではない

CORSは、「レスポンスをJavaScriptに渡すかどうか」を制御する仕組みです。単純リクエストは、CORSの設定に関係なくサーバーに届き、処理されます。フォームからのPOSTのような、利用者の意図しない操作(CSRF)を防ぐには、CSRFトークンやCookieのSameSite属性など、別の対策が必要です。

Cookieを送る場合の追加の注意点

別のオリジンのAPIにCookieを送るには、CORSの設定に加えて、Cookie自体の設定も必要です。

  • フロントエンドで credentials: "include"(axiosなら withCredentials: true)を指定する
  • サーバーは、具体的なオリジン+Access-Control-Allow-Credentials: true を返す
  • サイトをまたいでCookieを送る場合は、Cookieに SameSite=None; Secure が必要(HTTPSが前提)

ブラウザによっては、サイトをまたぐCookie(サードパーティCookie)自体を制限しています。フロントエンドとAPIを同じサイト(例:app.example.comとapi.example.com)に置く構成にしておくと、こうした影響を受けにくくなります。

よくある質問

Q. CORSエラーはフロントエンドで解決できますか? A. 基本的にはできません。ブラウザは、サーバーが返すヘッダーを見て判断するため、APIサーバー側の設定が必要です。自分で管理していない外部APIの場合は、自分のサーバーを経由して呼び出す(サーバー間の通信にはCORSが関係しない)方法をとります。

Q. 画像やフォント、iframeでもCORSは関係しますか? A. <img> タグで画像を表示するだけなら関係しません。ただし、画像をCanvasに描いて読み取る場合や、Webフォントを別のオリジンから読み込む場合は、CORSの設定が必要です。

Q. 設定を直したのに、エラーが変わりません。 A. プリフライトの結果がキャッシュされている(Access-Control-Max-Age)、またはCDNにレスポンスがキャッシュされている可能性があります。開発者ツールでキャッシュを無効にして再読み込みするか、CDNのキャッシュを削除してください。

Q. localhostで開発していると、なぜCORSエラーになるのですか? A. フロントエンドとAPIでポート番号が違うと、別のオリジンになるためです。開発サーバーのプロキシを使うか、APIでlocalhostのオリジンを許可します(本番の設定に開発用のオリジンを残さないよう注意します)。

まとめ

  • CORSエラーは、ブラウザが別オリジンのレスポンスをJavaScriptに渡さないために起きる。curlでは起きない
  • 解決するのはAPIサーバー側。許可するオリジンをヘッダーで返す
  • JSONのPOSTやAuthorizationヘッダー付きはプリフライト(OPTIONS)になる。OPTIONSを認証の対象外に
  • エラー時にヘッダーがないと、本当のエラーがCORSエラーに見える。Networkタブでステータスを確認する
  • Cookieを送るなら、* は使えない。具体的なオリジン+Allow-Credentials: true+SameSite=None; Secure
  • 開発時はプロキシで同じオリジンにする。no-cors やOriginの丸ごと許可は使わない

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CORSエラーの原因と解決方法|エラーメッセージ別に解説」

株式会社RJCでは、エラーメッセージの意味を仕組みから理解することを大切にしています。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?