1. はじめに
フロントエンドが localhost:3000、バックエンドが localhost:8080 で動いているアプリを作っているとします。一見問題なさそうに見えても、コンソールを開くとこんなエラーが表示されることがあります:
Access to fetch at 'http://localhost:8080/api/user' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
これがCORSエラーです — Web開発において最もよく遭遇するエラーの一つです。この記事では、CORSが何なのか、なぜ存在するのか、そして正しい対処法について解説します。
Cross-Origin Resource Sharing(CORS) とは、サーバーがブラウザに対して、どのオリジンからのリソースアクセスを許可するかを指定できるHTTPベースの仕組みです。
CORSは、ブラウザのセキュリティの基盤である Same-Origin Policy(SOP) を拡張するために設計されており、SOPはデフォルトで異なるオリジン間のリクエストをブロックします。
実際の開発では、フロントエンドとバックエンドが異なるドメインやポートにデプロイされている場合によく発生します。
2. 基本概念
2.1 オリジンとは?
オリジンは以下の3つの要素で構成されます:スキーム + ホスト + ポート
| URL | オリジン |
|---|---|
https://example.com |
https + example.com + 443 |
http://example.com |
http + example.com + 80 |
https://example.com:3000 |
https + example.com + 3000 |
2つのURLが「同じオリジン」と見なされるのは、上記3要素がすべて一致している場合のみです。
2.2 Same-Origin Policy
Same-Origin Policyはブラウザのセキュリティ機能です:
- リクエストの送信は許可する
- ただし、オリジンが異なる場合はレスポンスの読み取りをブロックする
これにより、あるWebページが制御なしに別のオリジンのデータにアクセスすることを防いでいます。
重要: リクエスト自体はサーバーに届きます。ブラウザが判断するのは、JavaScriptがそのレスポンスを読み取れるかどうかです。
2.3 なぜCORSが必要なのか?
Same-Origin Policyはセキュリティ向上に役立ちますが、正当なユースケースでも制限が生じることがあります:
- フロントエンドとバックエンドが異なるドメインで動作している
- サードパーティAPIを利用している
- マイクロサービスアーキテクチャを採用している
CORSは、SOPのデフォルト動作(全ブロック)の代わりに、サーバーが許可するオリジンを明示的に指定できるようにするために導入されました。
3. CORSの仕組み
CORSはHTTPヘッダーを使用して、異なるオリジンからのリソースアクセスを許可または拒否します。
3.1 基本的な流れ
- ブラウザがヘッダー付きでリクエストを送信:
Origin: https://example.com
- サーバーがレスポンスを返す:
Access-Control-Allow-Origin: https://example.com
- ブラウザが確認する:
- オリジンが有効 → レスポンスの読み取りを許可
- 無効 → レスポンスをブロック
この確認処理はすべてブラウザ側で行われます。
4. CORSにおけるリクエストの種類
すべてのリクエストが同じように処理されるわけではありません。
4.1 シンプルリクエスト
以下の条件を満たすリクエストは「シンプルリクエスト」と見なされます:
- メソッド: GET、POST、HEAD
- カスタムヘッダーなし
-
Content-Type が以下のいずれか:
application/x-www-form-urlencoded、multipart/form-data、text/plain
➡️ シンプルリクエストの場合:ブラウザは直接リクエストを送信し、プリフライトは発生しません。
4.2 プリフライトリクエスト
シンプルリクエストの条件を満たさない場合、ブラウザはまず OPTIONSリクエスト を送信します:
OPTIONS /api/data
Origin: https://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
サーバーのレスポンス:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type
レスポンスが有効であれば、ブラウザは本来のリクエストを送信します。
開発中に
OPTIONSリクエストを処理していないことが、CORSエラーのよくある原因です。
4.3 CORSで使用されるヘッダー
リクエストヘッダー(ブラウザが送信):
| ヘッダー | 説明 |
|---|---|
Origin |
リクエストのオリジンを示す |
Access-Control-Request-Method |
本リクエストのメソッド(プリフライト時) |
Access-Control-Request-Headers |
送信予定のカスタムヘッダー |
レスポンスヘッダー(サーバーが返す):
| ヘッダー | 説明 |
|---|---|
Access-Control-Allow-Origin |
アクセスを許可するオリジン |
Access-Control-Allow-Methods |
許可するメソッド |
Access-Control-Allow-Headers |
許可するヘッダー |
Access-Control-Allow-Credentials |
Cookieや認証情報の送信を許可する |
Access-Control-Max-Age |
プリフライトのキャッシュ時間 |
5. CredentialsとCORS
Cookie や Authorization ヘッダーなどの認証情報を含むリクエストの場合:
fetch("https://api.example.com", {
credentials: "include"
})
サーバー側で以下の設定が必要です:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
注意:
Allow-Credentials: trueの場合、*は使用できません。オリジンを具体的に指定する必要があります。
6. 実践例
基本的な例
リクエスト:
GET /api/user
Origin: https://frontend.com
レスポンス:
Access-Control-Allow-Origin: https://frontend.com
ブラウザはJavaScriptがレスポンスデータを読み取ることを許可します。
サーバー側の設定例
Node.js(Express):
const cors = require('cors');
app.use(cors({
origin: ['https://yourdomain.com', 'https://app.yourdomain.com'],
methods: ['GET', 'POST', 'PUT'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
Nginx:
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
if ($request_method = OPTIONS) {
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
}
7. よくある誤解
7.1 「CORSはサーバーを保護する」
CORSはリクエストがサーバーに届くことを防ぎません。ブラウザのJavaScriptがレスポンスを読み取れないようにするだけです。
7.2 「バックエンドの設定だけで十分」
CORSはブラウザが実施する仕組みです。curl や Postman などのツールには影響しません。
7.3 「* を使っても安全」
* はパブリックAPIには適切な場合もありますが、機密データを扱うAPIには使用すべきではありません。
8. ベストプラクティス
8.1 オリジンを具体的に指定する
Access-Control-Allow-Origin: https://yourdomain.com
8.2 メソッドとヘッダーを制限する
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
8.3 null をホワイトリストに入れない
null オリジンは file:// やサンドボックス化されたiframeから発生する可能性があり、セキュリティリスクになります。
8.4 プリフライトをキャッシュする
Access-Control-Max-Age: 86400
プリフライトリクエストはアプリケーションデータを含みませんが、ネットワークとサーバーのリソースを消費します。キャッシュすることでOPTIONSリクエストの数を減らせます。
8.5 環境ごとに設定を変える
const allowedOrigins = process.env.NODE_ENV === 'production'
? ['https://yourdomain.com']
: ['http://localhost:3000', 'http://localhost:8000'];
9. CORSのデバッグ
CORSエラーが発生した場合、以下のパターンのいずれかに該当することが多いです:
よくあるエラーメッセージ:
// エラー1: サーバーがCORSヘッダーを返していない
Access to fetch at '...' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
// エラー2: オリジンが許可されていない
The 'Access-Control-Allow-Origin' header has a value 'https://other.com'
that is not equal to the supplied origin.
// エラー3: プリフライトが失敗
Response to preflight request doesn't pass access control check.
デバッグチェックリスト:
- リクエストの
Originヘッダーを確認する(DevToolsのNetworkタブ) - レスポンスに
Access-Control-Allow-Originが含まれているか確認する - プリフライトがある場合:
OPTIONSリクエストが200/204を返しているか? - credentialsを使用している場合:
Allow-Credentials: trueになっており、オリジンが*ではなく具体的に指定されているか?
多くの場合、リクエスト自体は正常に届いていますが、ブラウザ側でレスポンスの読み取りがブロックされています。NetworkタブでOPTIONSリクエストも含めて詳しく確認しましょう。
10. 関連するセキュリティメカニズム
CORSは単独で動作するわけではありません:
- Same-Origin Policy:CORSが拡張する基盤となるポリシー
- CSRF:SOPとCORSはCSRFのリスクを軽減しますが、CSRFトークンの代替にはなりません
- Content Security Policy(CSP):読み込むリソースのソースを制御し、CORSを補完します
- Cookie/認証:CORSはレスポンスを読めるオリジンを制御しますが、サーバー側での独立した認証・認可も引き続き必要です
11. CORSでできないこと
CORSは包括的なセキュリティ機構ではありません。ブラウザが異なるオリジンからのレスポンスをJavaScriptに読み取らせるかどうかを制御するだけです。
以下のことはできません:
- サーバーへのリクエスト送信を防ぐ
- JavaScriptの実行を制御する
- XSSなどの攻撃から保護する
- 認証・認可の代替にはならない — サーバーはtoken、sessionなどで独立してアクセス権を検証する必要があります
そのため、CORSはCSPなど他のセキュリティメカニズムと組み合わせて使用されるのが一般的です。
12. 参考資料
- Cross-Origin Resource Sharing (CORS) - MDN Web Docs
- Same-Origin Policy (SOP) - MDN Web Docs
- Origin - MDN Web Docs
13. まとめ
CORSはブラウザセキュリティにおいて重要な仕組みです:
- Same-Origin Policyを拡張する
- HTTPヘッダーをベースにしている
-
サーバーではなくブラウザが実施する
CORSを正しく理解することで、エラーの効率的なデバッグ、正確なシステム設定、そしてよくある誤解の回避につながります。特に重要なのは、CORSは完全なセキュリティの壁ではなく、全体的なセキュリティ対策の一層に過ぎないという点です。