CORSを説明 curlで動作するがブラウザではエラーになる理由
CORSエラーはサーバーのバグのように感じますが、実際にはそうではありません。それはブラウザが強制的に実行する許可チェックです。同じオリジンポリシー、プリフライトリクエスト、そして重要な5つのヘッダーを理解し、すべてのCORSエラーを5分以内に解決してください。
あなたは書く fetch() 呼び出し。curlで完全に動作します。ブラウザを開き、エンドポイントにアクセスすると、次のようになります:
Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
Googleで調べます。Stack Overflowはヘッダーを追加するように教えてくれます。ヘッダーを追加します。別のエラーが発生します。別のヘッダーを追加します。するとプリフライトエラーになります。2時間経って、2016年の答えにあったフラグを使ってChromeでCORSを完全に無効にします。そのままでプロダクションにデプロイし、誰も気づかないことを願います。
この記事は、そのようなことが起こらないようにするためのものです。CORSは約10分で理解でき、理解した後は、毎回2分で解決できます。
これはサーバーのバグではありません。これはブラウザのルールです。
CORSについて理解すべき最も重要なことは次の通りです: ブラウザが完全に制御しています。サーバーがリクエストをブロッキングしません。ブラウザがサーバーがすでに応答した後で、その応答をJavaScriptに渡すかどうかをチェックし、不適切であれば応答を破棄し、エラーを投げます——もしそのリクエストが完了し、サーバーがデータを返した場合でもです。
それがcurlが動作する理由です。curlはCORSを強制しません。Postmanも同様です。バックエンドが別のバックエンドにリクエストを送る場合も同じです。CORSは、ウェブページの代わりにブラウザがクロスオリジンリクエストを送るときにのみ活性化します。
サーバーの役割は、クロスオリジンアクセスを許可するかどうかを、応答ヘッダーで宣言することです。ブラウザの役割は、そのヘッダーをチェックし、JavaScriptに応答を渡すかどうかを決定することです。ヘッダーが間違っている場合、ブラウザは応答を破棄し、エラーを投げます——もしそのリクエストが完了し、サーバーがデータを返した場合でもです。 許可 クロスオリジンアクセスは応答ヘッダーを通じて行われます。ブラウザの役割は、そのヘッダーを確認し、JavaScriptに応答を渡すかどうかを決定することです。ヘッダーが正しくない場合、ブラウザは応答を破棄し、エラーを投げます。しかし、リクエストは完了し、サーバーがデータを返しているにもかかわらずです。
ブラウザがなぜそうするのか(同じオリジンポリシー)
同じオリジンポリシー(SOP)は、JavaScriptが https://evil.com から読み取るレスポンスを https://yourbank.comに読み取るのを防ぐブラウザのセキュリティルールです。なければ、どのサイトもあなたの代わりに認証リクエストを静かに送り、結果を読み取ることができます。
2つのURLが同じオリジンであるとは、その スキーム、ホスト、ポート がすべて一致することです:
https://app.example.comとhttps://api.example.com— オリジンが異なる(サブドメインが異なる)http://example.comとhttps://example.com— オリジンが異なる(スキームが異なる)https://example.comとhttps://example.com:8080— オリジンが異なる(ポートが異なる)https://example.com/fooとhttps://example.com/bar— オリジンが同じ(パスは無関係)
CORSは、サーバーが特定のオリジンに対してSOPを緩和することを許可する仕組みです。これは回避ではなく、明示的な許可の付与です。
シンプルリクエストとプリフライトリクエスト
すべてのクロスオリジンリクエストがプリフライトをトリガーするわけではありません。ブラウザはリクエストを2つのカテゴリに分類します。
は、GETまたはPOSTリクエストで、テキストまたはフォームエンコードされたボディ、および少数の許可されたヘッダーを使用しています。ブラウザは直接それらを送信し、応答に 直接通過(最初にOPTIONSチェックを行わない)。リクエストが「シンプル」であるとは、次のすべてを満たす場合です:
- メソッドがGET、POST、またはHEAD
- Content-Typeが
application/x-www-form-urlencoded,multipart/form-data、 またはtext/plain - カスタムヘッダーは、 CORSが安全にリストされたものに限る
プリフライトリクエスト は、それ以外のすべて——PUT/PATCH/DELETE、任意の application/json ボディ、任意のカスタムヘッダー(例: Authorization または X-API-Key)。実際のリクエストの前に、ブラウザは自動的にOPTIONSリクエストを送り、「これは許可されますか?」と尋ねます。
プリフライトが実際にどのように見えるか
ここに実際のプリフライト交換があります。あなたのフロントエンドが fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' } }).
に呼び出します。ブラウザは最初にこのOPTIONSリクエストを自動的に送ります——あなたがこのコードを書く必要はありません:
OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://yourapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
サーバーは、このリクエストを許可するヘッダーを返す必要があります:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
プリフライトが成功した場合にのみ、ブラウザが実際のPOSTを送ります。プリフライトヘッダーが欠落または誤っている場合、実際のリクエストはブラウザ内で決して送られません。
Access-Control-Max-Age: 86400 24時間キャッシュし、ブラウザが毎回このハンドシェイクを繰り返すのを防ぎます。これは設定すべきです。
実際に重要なヘッダー
Access-Control-Allow-Origin — 唯一の必須ヘッダー。特定のオリジン(https://yourapp.com)または * )を指定するか、任意のオリジン( * )を指定する場合があります。認証(クッキー、Authorizationヘッダー)を送信する場合は、
Access-Control-Allow-Methods — プリフライトリクエストに必要なヘッダー。許可したいすべてのメソッドをリストしてください。OPTIONSを常に含めるか、そうでない場合、サーバーはプリフライトに対して405を返します。
Access-Control-Allow-Headers — カスタムヘッダーを含む場合に必要なヘッダー。あなたのフロントエンドが送信するすべての非安全リストヘッダーを明示的にリストしてください。欠落した Authorization は、CORSサポートチケットの約40%を引き起こします。
Access-Control-Allow-Credentials: true — クッキーまたはHTTP認証を送信する場合にのみ必要なヘッダー。また、 credentials: 'include' fetch呼び出しに Allow-Origin を明示的に設定する必要があります。この設定がtrueの場合、 * は明示的なオリジンでなければなりません。
Access-Control-Expose-Headers — フロントエンドが読み取れる応答ヘッダー。デフォルトではJavaScriptはContent-Type、Cache-Controlなど、小さな安全リストのみを読み取れます。必要であれば、応答ハンドラーで読み取るカスタムヘッダー(例: X-Request-Id )をここに追加します。
5つの時間のロスを引き起こす誤り
1. 認証付きリクエストでのワイルドカード。 設定 Access-Control-Allow-Origin: * とすると、なぜ認証付きリクエストがまだ失敗するのかと気まずいです。仕様は、認証が関与する場合にワイルドカードを明確に禁止しています。特定のオリジンを反映する必要があります:リクエストヘッダーを読み取り、応答ヘッダーに再現する必要があります。 Origin 2. OPTIONSをまったく処理しない。
Express、FastAPI、そしてほとんどのフレームワークは、OPTIONSリクエストに対してCORSヘッダーを自動的に返すことはありません。もしCORSミドルウェアが実際のエンドポイントのみに適用され、プリフライトルートには適用されない場合、すべての認証付きまたはシンプルでないリクエストが失敗します。解決策:CORSミドルウェアをルーターの前に適用するか、OPTIONSを明示的に処理する必要があります。 3. CORSヘッダーを成功時にのみ返す。
サーバーが4xxまたは5xxを返した場合、CORSヘッダーを返さない場合、ブラウザはJavaScriptに実際のステータスコードを隠します。エラーハンドラーは、ステータスコードなしの一般的なネットワークエラーを受け取ります。すべての応答に を返す必要があります。 Access-Control-Allow-Origin 4. ポートを忘れる。
は異なるオリジンです。ローカル開発中に、3000番ポートのフロントエンドから8080番ポートのバックエンドにアクセスする場合があります。両方のポートを許容オリジンリストに含める必要があります、または開発プロキシが必要です。 https://app.example.com と https://app.example.com:3000 5. バッドプリフライトをキャッシュする。
プリフライトをキャッシュします。86400に設定した場合、許可ヘッダーを更新しても、キャッシュされた(誤った)プリフライトを持つブラウザは、最大24時間間、失敗を繰り返します。開発中は、これを0に設定するか、ハードリロード+開発ツールキャッシュを無効にする必要があります。 Access-Control-Max-Age プリフライトをキャッシュします。86400に設定した場合、許可されたヘッダーを更新すると、キャッシュされた(誤った)プリフライトを持つブラウザは最大24時間間、失敗を繰り返します。開発中では、この値を0に設定するか、強制リロードと開発ツールのキャッシュを無効にすることで対処してください。
シナリオ別の迅速な解決策
異なるドメインのフロントエンドとバックエンド、クッキーなし: 追加する Access-Control-Allow-Origin: * (または特定のフロントエンドオリジン) をサーバーに送信するだけです。完了。
異なるドメインのフロントエンドとバックエンド、クッキー/認証あり: あなたは、 Access-Control-Allow-Origin: https://yourfrontend.com (明示的、ワイルドカードなし) および Access-Control-Allow-Credentials: true をサーバーに設定する必要があります、さらに credentials: 'include' をfetch呼び出しに設定する必要があります。クッキーも SameSite=None; Secure をクロスオリジンに設定する必要があります。
ローカル開発が制御できないリモートAPIにアクセスする場合: ビルドツールに開発プロキシを追加します。Vite: server.proxy。webpack-dev-server: devServer.proxy。Next.js: rewrites。プロキシにより、ブラウザはすべてのリクエストを同じオリジンと見なします。CORSは発動しません。
APIゲートウェイ/CDNの前に: OPTIONSプリフライトが元のサーバーに到達する前に吸収されないことを確認してください。AWS API GatewayおよびCloudFrontは、CORS設定がアプリレベルのヘッダーと衝突する可能性があります——両方の場所にヘッダーを設定すると、重複するヘッダー値が発生し、失敗します。
正確なヘッダーを確認したい場合は、 CORSヘッダービルダー&バリデーター IO Tools は、コードを1行書かずに、フルヘッダーを構築し、確認できます。バックエンドが実際に返すものと、あなたが思っているものとの差を確認するために役立ちます。
CORSエラーはサーバーのバグのように見えますが、エラーメッセージにサーバーが含まれているためです。これはブラウザが強制する許可チェックであり、どのヘッダーが欠けているかとその理由を理解すれば、すべてのCORSエラーは数分で解決できます。適切なヘッダーを構築するには、 CORSヘッダービルダー または、クライアント側のミスを排除するために、 cURLコマンドビルダー を使用して、サーバー設定を変更する前に、rawリクエスト構造をテストします。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
