【Web開発】CORSエラー完全攻略ガイド!原因特定から解決策まで徹底解説 CORS is killing me…

Web開発

はじめに:イライラするCORSエラーとの付き合い方 🤔

Web開発、特にフロントエンドとバックエンドが分離した構成(APIサーバーと通信するSPAなど)で開発を進めていると、多くの開発者が一度は遭遇するであろう厄介な問題、それが CORS (Cross-Origin Resource Sharing) エラーです。

ブラウザのコンソールに突如現れる赤いエラーメッセージ…

Access to fetch at ‘http://api.example.com/data’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: 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.

「またか…」とうんざりした経験はありませんか? 😩 CORSエラーは、一見すると複雑で、何が原因なのか特定しづらいことがあります。しかし、その仕組みと原因、そして対処法を正しく理解すれば、決して恐れる必要はありません。

この記事では、CORSエラーが発生する根本的な理由から、具体的なエラーの種類、原因特定のステップ、そしてサーバーサイド・クライアントサイドでの実践的な解決策まで、順序立てて詳しく解説していきます。この記事を読み終える頃には、CORSエラーに冷静に対処できるようになっているはずです! 💪

CORSエラーの解決策を探る前に、まず「CORSとは何か」「なぜ存在するのか」という基本的な部分を理解することが非常に重要です。

1.1. オリジン (Origin) とは?

CORSを理解する上でのキーワードは 「オリジン (Origin)」 です。オリジンとは、URLの以下の3つの要素の組み合わせを指します。

  • スキーム (Scheme): `http` や `https` など
  • ホスト (Host): `www.example.com`, `localhost`, `127.0.0.1` など
  • ポート (Port): `:80`, `:443`, `:3000`, `:8080` など (省略されている場合はデフォルトポート、httpなら80, httpsなら443)

例えば、以下のURLはすべて異なるオリジンとみなされます。

  • `http://example.com`
  • `https://example.com` (スキームが違う)
  • `http://www.example.com` (ホストが違う)
  • `http://example.com:8080` (ポートが違う)
  • `http://localhost:3000`

一方で、以下のURLは同じオリジンとみなされます。

  • `http://example.com/page1`
  • `http://example.com/page2/subpage`
  • `http://example.com/` (パスが違ってもオリジンは同じ)

1.2. 同一オリジンポリシー (Same-Origin Policy: SOP)

Webブラウザには、セキュリティの基盤となる 「同一オリジンポリシー (Same-Origin Policy, SOP)」 という重要な仕組みが組み込まれています。これは、あるオリジンから読み込まれた文書やスクリプトが、他のオリジンのリソースに対してアクセス制限をかけるというものです。

もしSOPがなければ、悪意のあるWebサイト (例: `http://evil.com`) が、ユーザーがログインしている別のサイト (例: `http://mybank.com`) の情報をJavaScriptを使って勝手に読み取ったり、操作したりできてしまいます。これは非常に危険ですよね? 😱 SOPは、このような悪意のある行為(例えば、クロスサイトスクリプティング (XSS) の一部やクロスサイトリクエストフォージェリ (CSRF) の助長)を防ぐための基本的な防御策なのです。

1.3. CORS (Cross-Origin Resource Sharing) の登場

しかし、現代のWebアプリケーションでは、異なるオリジン間でリソースを共有する必要性が高まっています。例えば、

  • フロントエンド (例: `http://app.example.com`) が、バックエンドのAPIサーバー (例: `http://api.example.com`) からデータを取得する。
  • 外部のAPI (例: 天気情報API, 地図API) を利用する。
  • CDN (Content Delivery Network) からフォントや画像などの静的ファイルを読み込む。

SOPが厳格すぎると、これらの正当な要求までブロックされてしまいます。そこで登場したのが CORS (Cross-Origin Resource Sharing) です。

CORSは、サーバー側が特定のオリジンからのリクエストを許可するための仕組みです。ブラウザは、異なるオリジンへのリクエストを行う際に、サーバーからの応答ヘッダーを見て、そのリクエストが許可されているかどうかを判断します。許可されていれば通信を続行し、許可されていなければエラー(=CORSエラー)を発生させて通信をブロックします。

💡 重要ポイント: CORSエラーは、サーバーがリクエストを拒否しているのではなく、ブラウザがSOPに基づき、サーバーからの許可がないクロスオリジンリクエストのレスポンスをブロックしている状態です。サーバー自体はリクエストを処理してレスポンスを返している場合もありますが、ブラウザがそれをJavaScriptに渡さないのです。

CORSにおけるリクエストは、大きく分けて「シンプルリクエスト」と「プリフライトリクエスト」の2種類があります。どちらのリクエストになるかは、リクエストのメソッドやヘッダーによって決まります。この違いを理解することが、トラブルシューティングの鍵となります。

2.1. シンプルリクエスト (Simple Requests)

特定の条件を満たすリクエストは「シンプルリクエスト」として扱われ、事前の確認なしに直接リクエストが送信されます。

シンプルリクエストとみなされる条件は以下のすべてを満たす場合です。

  • メソッドが以下のいずれかであること:
    • GET
    • HEAD
    • POST
  • 手動で設定できるヘッダーが以下のいずれかのみであること:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (ただし、値が以下のいずれかの場合のみ)
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
    (その他、ブラウザが自動的に付与するヘッダー、例えば `Host`, `User-Agent` などは許容されます)
  • リクエストに使用されるどの XMLHttpRequestUpload オブジェクトにもイベントリスナーが登録されていないこと。(通常、fetchや基本的なXMLHttpRequestの使用では問題になりません)
  • リクエストに ReadableStream オブジェクトが使用されていないこと。

シンプルリクエストの場合、ブラウザは直接リクエストを送信し、サーバーからのレスポンスに Access-Control-Allow-Origin ヘッダーが含まれているか、含まれている場合はその値がリクエスト元のオリジンと一致するか(または * か)を確認します。

⚠️ Content-Type: application/json を使ってPOSTリクエストを送る場合、これはシンプルリクエストの条件を満たさないため、後述するプリフライトリクエストが発生します。これは非常によくあるケースです。

2.2. プリフライトリクエスト (Preflight Requests)

シンプルリクエストの条件を満たさないリクエストは、「プリフライトリクエスト」が必要になります。これは、実際のリクエストを送信する前に、ブラウザがサーバーに対して「これからこういうリクエストを送っても大丈夫?」とお伺いを立てるためのリクエストです。

プリフライトリクエストは、以下の特徴を持ちます。

  • HTTPメソッドは OPTIONS を使用します。
  • 以下のヘッダーを含みます:
    • Origin: リクエスト元のオリジン
    • Access-Control-Request-Method: 実際のリクエストで使用するメソッド (例: PUT, DELETE, POST (Content-Typeが特殊な場合など))
    • Access-Control-Request-Headers: 実際のリクエストで使用するカスタムヘッダー (例: Authorization, X-Custom-Header)

サーバーは、この OPTIONS リクエストを受け取り、実際のリクエストを許可するかどうかを判断し、以下のヘッダーを含むレスポンスを返します。

  • Access-Control-Allow-Origin: 許可するオリジン (リクエスト元のオリジンまたは *)
  • Access-Control-Allow-Methods: 許可するメソッド (例: GET, POST, PUT, DELETE, OPTIONS)
  • Access-Control-Allow-Headers: 許可するヘッダー (例: Content-Type, Authorization, X-Custom-Header)
  • (オプション) Access-Control-Allow-Credentials: Cookieなどの認証情報を含むリクエストを許可するかどうか (true)
  • (オプション) Access-Control-Max-Age: プリフライトリクエストの結果をキャッシュする時間(秒)。この時間内は再度プリフライトリクエストを送らずに済みます。

ブラウザは、プリフライトリクエストのレスポンスヘッダーを見て、これから送ろうとしている実際のリクエストが許可されていると判断した場合にのみ、実際のリクエスト (例: PUT, DELETE) を送信します。もしプリフライトリクエストが失敗した場合(必要なヘッダーがない、値が不適切など)、ブラウザは実際のリクエストを送信せずにCORSエラーを表示します。

💡 プリフライトリクエストの仕組みを理解することは、PUTDELETE メソッド、カスタムヘッダー (Authorizationなど) を使用するAPI通信でCORSエラーが発生した場合のトラブルシューティングに不可欠です。

CORSエラーに遭遇したら、慌てずに原因を特定するためのステップを踏みましょう。

3.1. 開発者ツールのコンソールを確認する

まず最初に確認すべきは、ブラウザの開発者ツール(多くのブラウザでF12キーで開けます)の 「コンソール (Console)」 タブです。ここに、CORSエラーに関する具体的なメッセージが表示されています。

よく見られるエラーメッセージの例と、その意味するところを見てみましょう。

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

    → 最も一般的なエラー。サーバーからのレスポンスに Access-Control-Allow-Origin ヘッダーが含まれていないことを示します。サーバー側での設定漏れが考えられます。

  • The 'Access-Control-Allow-Origin' header has a value 'http://some-other-origin.com' that is not equal to the supplied origin.

    → サーバーは Access-Control-Allow-Origin ヘッダーを返していますが、その値がリクエスト元のオリジンと一致していません。サーバー側で許可するオリジンの設定が間違っているか、意図しないオリジンが設定されています。

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

    Access-Control-Allow-Origin ヘッダーに複数の値が設定されています。このヘッダーには単一のオリジンか * のいずれかしか指定できません。サーバー側の設定、あるいはプロキシなどが余計なヘッダーを追加している可能性があります。

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

    → プリフライトリクエスト (OPTIONS) に対するレスポンスで、許可されるメソッド (Access-Control-Allow-Methods) に、実際のリクエストで使おうとしているメソッド (この例では PUT) が含まれていません。サーバー側の OPTIONS メソッドのハンドリングと、Access-Control-Allow-Methods ヘッダーの設定を確認する必要があります。

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

    → プリフライトリクエスト (OPTIONS) に対するレスポンスで、許可されるヘッダー (Access-Control-Allow-Headers) に、実際のリクエストで使おうとしているヘッダー (この例では Authorization) が含まれていません。サーバー側の OPTIONS メソッドのハンドリングと、Access-Control-Allow-Headers ヘッダーの設定を確認する必要があります。

  • Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. or 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など) を含むリクエスト (fetchcredentials: 'include' など) を送ろうとしていますが、サーバー側の設定が適切ではありません。Access-Control-Allow-Credentials: true をレスポンスヘッダーに含める必要があり、かつ、その場合 Access-Control-Allow-Origin にワイルドカード * は使用できません。特定のオリジンを指定する必要があります。

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

    → プリフライトリクエスト (OPTIONS) に対するレスポンスのHTTPステータスコードが 2xx (例: 200 OK, 204 No Content) ではありません。サーバー側で OPTIONS メソッドのリクエストを正しく処理し、成功ステータスを返すように設定する必要があります。フレームワークによっては、ルーティング設定で OPTIONS メソッドを明示的に許可する必要がある場合があります。

コンソールのエラーメッセージは、原因を特定するための最も重要な手がかりです。焦らず、メッセージの内容をよく読み解きましょう。

3.2. 開発者ツールのネットワークタブを確認する

次に、開発者ツールの 「ネットワーク (Network)」 タブを確認します。ここで、実際に行われたHTTPリクエストとレスポンスの詳細を見ることができます。

  1. ページをリロードするか、問題の操作を行って、ネットワークタブに通信ログを表示させます。
  2. CORSエラーが発生しているリクエストを探します。通常、赤色で表示されたり、ステータスが `(failed)` や `CORS error` と表示されたりします。
  3. そのリクエストを選択し、「ヘッダー (Headers)」タブを確認します。
    • リクエストヘッダー (Request Headers): Origin, Access-Control-Request-Method, Access-Control-Request-Headers など、CORSに関連するヘッダーが正しく送信されているか確認します。
    • レスポンスヘッダー (Response Headers): サーバーから返された Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials などのヘッダーが存在するか、その値が期待通りかを確認します。
  4. プリフライトリクエストが発生している場合は、まず OPTIONS メソッドのリクエストとそのレスポンスを確認します。ここで問題があれば、実際のリクエストは送信されません。
  5. シンプルリクエストの場合や、プリフライトリクエストが成功した後の実際のリクエスト (例: GET, POST) のレスポンスヘッダーを確認します。特に Access-Control-Allow-Origin を重点的にチェックします。

💡 ネットワークタブでは、リクエストがサーバーに到達しているか、サーバーがどのようなレスポンスを返しているかを直接確認できます。コンソールエラーだけでは分からない詳細情報が得られます。

3.3. curl や Postman などでAPI単体をテストする

ブラウザを介さずに、curl コマンドや Postman のようなAPIテストツールを使って、直接APIサーバーにリクエストを送ってみることも有効です。

例えば、curl を使う場合:

# GETリクエストのテスト (-i でヘッダーも表示)
curl -i -H "Origin: http://localhost:3000" http://api.example.com/data

# プリフライトリクエスト(OPTIONS)のシミュレーション
curl -i -X OPTIONS -H "Origin: http://localhost:3000" \
     -H "Access-Control-Request-Method: PUT" \
     -H "Access-Control-Request-Headers: Content-Type, Authorization" \
     http://api.example.com/items/123

# 実際のリクエスト(PUT)のシミュレーション
curl -i -X PUT -H "Origin: http://localhost:3000" \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer your_token" \
     -d '{"name": "updated item"}' \
     http://api.example.com/items/123

これらのツールを使うことで、

  • サーバーが期待通りのCORS関連ヘッダーを返しているか?
  • サーバー側のAPIエンドポイント自体が正しく動作しているか?

を切り分けて確認できます。もし curl などでは正しくヘッダーが返ってくるのにブラウザでエラーになる場合は、ブラウザ側の問題(キャッシュ、拡張機能など)や、リクエストの送り方(JavaScriptコード)に問題がある可能性が考えられます。

3.4. サーバー側のログを確認する

サーバー側のアプリケーションログやWebサーバー (Nginx, Apacheなど) のアクセスログ・エラーログを確認することも重要です。

  • リクエストがサーバーに到達しているか?
  • サーバー側で何らかのエラーが発生していないか? (CORS設定以前の問題)
  • 意図した通りにCORS関連の処理(ミドルウェアなど)が動作しているか?

特に、プリフライトリクエスト (OPTIONS) がサーバー側のルーティングで処理されずに 404 Not Found や 405 Method Not Allowed になっていないかなどを確認できます。

3.5. ブラウザのキャッシュや拡張機能の影響を確認する

稀なケースですが、ブラウザのキャッシュが古いCORS設定を記憶していたり、特定のブラウザ拡張機能がリクエスト/レスポンスヘッダーを変更してしまい、CORSエラーを引き起こすことがあります。

  • ブラウザのキャッシュをクリアしてみる。
  • シークレットモード/プライベートウィンドウで試してみる。(拡張機能が無効になるため)
  • 一時的にブラウザ拡張機能をすべて無効にしてみる。

これで問題が解決する場合、キャッシュや特定の拡張機能が原因である可能性が高いです。

CORSエラーの根本的な解決策は、多くの場合、サーバーサイドで適切なCORSヘッダーをレスポンスに付与することです。ここでは、主要なヘッダーとその設定方法、および一般的なWebフレームワークでの実装例を紹介します。

4.1. `Access-Control-Allow-Origin` ヘッダー

最も重要なヘッダーです。どのオリジンからのリクエストを許可するかを指定します。

  • 特定のオリジンを許可する場合:
    Access-Control-Allow-Origin: http://localhost:3000
    Access-Control-Allow-Origin: https://app.example.com

    セキュリティ上、最も推奨される方法です。許可するオリジンを明示的に指定します。

  • 任意のオリジンを許可する場合 (非推奨の場合あり):
    Access-Control-Allow-Origin: *

    ワイルドカード * を指定すると、すべてのオリジンからのリクエストを許可します。公開APIなどで意図的に使用する場合を除き、セキュリティリスクを高める可能性があるため注意が必要です。特に、認証情報 (Cookieなど) を扱う場合は * は使用できません(後述)。

  • 動的にオリジンを設定する場合:

    リクエストヘッダーの Origin を見て、許可リストに含まれていればそのオリジンを Access-Control-Allow-Origin の値として返す方法です。複数のオリジンを許可したい場合に有効です。

4.2. `Access-Control-Allow-Methods` ヘッダー

プリフライトリクエスト (OPTIONS) に対するレスポンスで、許可するHTTPメソッドを指定します。

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

実際にAPIで使用するメソッドをカンマ区切りで列挙します。OPTIONS メソッド自体も許可リストに含めることが一般的です。

4.3. `Access-Control-Allow-Headers` ヘッダー

プリフライトリクエスト (OPTIONS) に対するレスポンスで、リクエストで使用することを許可するヘッダーを指定します。

Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-Custom-Header

Content-Type や認証用の Authorization ヘッダーなど、シンプルリクエストの条件外のヘッダーを使用する場合は、ここに含める必要があります。

4.4. `Access-Control-Allow-Credentials` ヘッダー

Cookie、Authorizationヘッダー、TLSクライアント証明書などの認証情報を含むリクエストを許可するかどうかを示します。

Access-Control-Allow-Credentials: true

このヘッダーを true に設定する場合、以下の重要な注意点があります。

  • Access-Control-Allow-Origin にワイルドカード * を指定することはできません。必ず特定のオリジンを指定する必要があります。
  • クライアント側でも、fetch の場合は credentials: 'include'XMLHttpRequest の場合は withCredentials = true を設定する必要があります(後述)。

4.5. `Access-Control-Expose-Headers` ヘッダー

デフォルトでは、ブラウザはシンプルレスポンスヘッダー(Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma)しかJavaScriptからアクセスできません。それ以外のレスポンスヘッダー(例: X-Pagination-Total-Count のようなカスタムヘッダー)にアクセスさせたい場合は、このヘッダーで許可する必要があります。

Access-Control-Expose-Headers: Content-Length, X-Pagination-Total-Count

4.6. `Access-Control-Max-Age` ヘッダー

プリフライトリクエストの結果をブラウザがキャッシュして良い秒数を指定します。

Access-Control-Max-Age: 86400

例えば 86400 (24時間) を指定すると、その間は同じリクエストに対して再度プリフライトリクエストが送信されなくなり、パフォーマンスが向上します。

4.7. Webフレームワークでの実装例 (Python/Flask)

多くのWebフレームワークでは、CORS設定を簡単に行うためのライブラリやミドルウェアが提供されています。ここでは、PythonのFlaskフレームワークで Flask-CORS ライブラリを使う例を示します。

まず、ライブラリをインストールします。

pip install Flask-CORS

そして、Flaskアプリケーションで設定します。

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)

# すべてのエンドポイントで、すべてのオリジンからのCORSを許可 (最も簡単な設定、本番環境では注意)
# CORS(app)

# 特定のエンドポイントで、特定のオリジンからのCORSを許可 (より安全な設定)
# resources にはパスと設定の辞書を指定
# origins に許可するオリジンを指定 (リスト形式も可)
# methods に許可するメソッドを指定
# allow_headers に許可するヘッダーを指定
# supports_credentials=True で Access-Control-Allow-Credentials: true を有効化
cors = CORS(app, resources={
    r"/api/*": {
        "origins": ["http://localhost:3000", "https://app.example.com"],
        "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        "allow_headers": ["Content-Type", "Authorization"],
        "supports_credentials": True
    }
})

@app.route('/api/data')
def get_data():
    # Cookieを伴うリクエストなどに対応するため、credentials=True と併用する場合は
    # Access-Control-Allow-Origin は * ではなく特定のオリジンを返す必要がある
    # Flask-CORS が自動で処理してくれる
    response = jsonify({'message': 'Hello from API!'})
    return response

@app.route('/api/items', methods=['POST', 'OPTIONS'])
def create_item():
    # OPTIONSリクエストはFlask-CORSが自動で処理してくれる
    if request.method == 'POST':
        # ... item作成処理 ...
        return jsonify({'message': 'Item created'}), 201
    # Flask-CORSを使わない場合、OPTIONSメソッドのハンドラを自分で書く必要がある
    # elif request.method == 'OPTIONS':
    #     headers = {
    #         'Access-Control-Allow-Origin': 'http://localhost:3000', # 動的に設定推奨
    #         'Access-Control-Allow-Methods': 'POST, OPTIONS',
    #         'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    #         'Access-Control-Max-Age': '3600'
    #     }
    #     return ('', 204, headers)


if __name__ == '__main__':
    app.run(debug=True, port=5000)

他のフレームワーク(Node.js/Expressの `cors` ミドルウェア、Ruby on Railsの `rack-cors` gem、PHP/Laravelの `fruitcake/laravel-cors` パッケージなど)でも同様のライブラリが存在します。公式ドキュメントを参照して、適切な設定を行ってください。

参考リンク:

4.8. Webサーバー/リバースプロキシでの設定 (Nginx)

アプリケーションサーバーの前段にNginxなどのリバースプロキシを置いている場合、そこでCORSヘッダーを付与することも可能です。

server {
    listen 80;
    server_name api.example.com;

    location / {
        # 許可したいオリジン (正規表現も利用可能)
        set $cors_origin "";
        if ($http_origin ~* (https?://localhost:3000|https?://app\.example\.com)) {
            set $cors_origin $http_origin;
        }

        # OPTIONS (プリフライト) リクエストへの対応
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' $cors_origin always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
            add_header 'Access-Control-Allow-Credentials' 'true' always; # 必要に応じて
            add_header 'Access-Control-Max-Age' 86400 always;
            add_header 'Content-Length' 0;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            return 204; # 204 No Content を返す
        }

        # 実際のリクエストへのCORSヘッダー付与
        # always をつけると、エラーレスポンス(4xx, 5xx)にもヘッダーが付与される
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always; # 必要に応じて
        add_header 'Access-Control-Expose-Headers' 'Content-Length, X-Pagination-Total-Count' always; # 必要に応じて

        proxy_pass http://localhost:5000; # バックエンドのアプリケーションサーバー
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

⚠️ アプリケーションサーバーとリバースプロキシの両方でCORSヘッダーを設定すると、ヘッダーが重複したり、意図しない挙動になったりすることがあります。どちらか一方で管理するのが推奨されます。

サーバー側の設定が正しく行われていても、クライアントサイド(ブラウザで動作するJavaScript)の実装によってはCORSエラーが発生したり、意図した通信ができなかったりすることがあります。

5.1. 認証情報 (Credentials) の送信設定

サーバーが Access-Control-Allow-Credentials: true を返し、CookieやAuthorizationヘッダーなどの認証情報を含むクロスオリジンリクエストを行いたい場合、クライアント側でもその旨を指定する必要があります。

fetch API を使用する場合:

fetch('http://api.example.com/data', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    // Authorizationヘッダーなどもここに含まれる
  },
  // ↓ これが必要!
  credentials: 'include' // 'same-origin'(デフォルト), 'omit' もある
})
.then(response => {
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));

XMLHttpRequest を使用する場合:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://api.example.com/data', true);
xhr.setRequestHeader('Content-Type', 'application/json');
// ↓ これが必要!
xhr.withCredentials = true;

xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log(JSON.parse(xhr.responseText));
  } else {
    console.error('Request failed:', xhr.statusText);
  }
};
xhr.onerror = function() {
  console.error('Network error.');
};
xhr.send();

⚠️ credentials: 'include'withCredentials = true を設定する場合、サーバー側は Access-Control-Allow-Origin でワイルドカード * を使用できず、特定のオリジンを指定する必要があることを忘れないでください。

5.2. `no-cors` モードについて

fetch APIには mode: 'no-cors' というオプションがあります。

fetch('http://api.example.com/some-resource', {
  mode: 'no-cors' // CORSを無視してリクエストを送信
})
.then(response => {
  // レスポンスタイプが 'opaque' となり、中身にアクセスできない
  console.log(response.type); // "opaque"
  // console.log(response.status); // エラーになる (アクセス不可)
  // response.json() や response.text() も使えない
})
.catch(error => console.error('Fetch error:', error));

このモードを使うと、ブラウザはCORS関連のヘッダーをチェックせずにリクエストを送信します。一見、CORSエラーを回避できるように見えますが、レスポンスの中身 (本文、ステータスコード、ヘッダーなど) にJavaScriptからアクセスできなくなります(レスポンスタイプが `opaque` になる)。

用途としては、Service Workerによるキャッシュ目的や、単純にリクエストを送ること自体が目的(結果は不要)といった非常に限定的なケースに限られます。通常のAPI通信でデータを取得・利用したい場合には使えません。

5.3. 開発環境でのプロキシ設定

開発時(例: フロントエンドを `http://localhost:3000` で、APIサーバーを `http://localhost:5000` で動かしている場合)、フロントエンドの開発サーバーにプロキシ機能があれば、それを利用してCORSエラーを回避する方法があります。

例えば、Create React App や Vue CLI などでよく使われる `webpack-dev-server` にはプロキシ機能があります。

Create React App (package.json):

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": { ... },
  "scripts": { ... },
  "eslintConfig": { ... },
  "browserslist": { ... },
  "proxy": "http://localhost:5000" // APIサーバーのオリジン
}

このように設定すると、フロントエンドのJavaScriptからは相対パス (例: `/api/data`) でAPIを呼び出すことができます。

// http://localhost:3000/api/data へのリクエストになるが、
// 開発サーバーが http://localhost:5000/api/data へプロキシしてくれる
fetch('/api/data')
  .then(...)

開発サーバー (http://localhost:3000) が、/api/* へのリクエストを受け取ると、それをバックエンドのAPIサーバー (http://localhost:5000) へ転送し、そのレスポンスをクライアントに返します。ブラウザから見ると、常に同じオリジン (http://localhost:3000) と通信しているように見えるため、CORSエラーが発生しません。

⚠️ これはあくまで開発環境での回避策です。本番環境では、APIサーバー側で適切なCORS設定を行う必要があります。

CORSエラーはWeb開発において頻繁に遭遇する問題ですが、その背後にある同一オリジンポリシー (SOP) と、それを安全に緩和するための仕組みであるCORSを理解すれば、効果的に対処できます。

トラブルシューティングのポイントをおさらいしましょう:

  1. コンソールエラーを読む: エラーメッセージには原因を特定するための重要なヒントが含まれています。
  2. ネットワークタブを確認する: リクエストとレスポンスのヘッダー(特に Origin, Access-Control-*関連)を詳細にチェックします。プリフライトリクエスト (OPTIONS) が発生しているか、その成否も確認します。
  3. API単体テスト: curl や Postman で、ブラウザを介さずにAPIサーバーの挙動を確認します。
  4. サーバー設定を確認・修正する: Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials などのヘッダーを、サーバー側のコードやミドルウェア、リバースプロキシ設定で正しく付与します。プリフライトリクエスト (OPTIONS) に適切に応答できているかも確認します。
  5. クライアント設定を確認する: 認証情報を含むリクエストの場合、credentials: 'include'withCredentials = true が適切に設定されているか確認します。

✅ CORSの仕組みはWebセキュリティの基本であり、正しく理解して設定することは、安全なWebアプリケーションを構築するために不可欠です。エラーに遭遇しても焦らず、この記事で解説したステップに従って原因を特定し、適切な解決策を適用してください。

Happy Coding! 🎉

コメント

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