CORS を解説:モダンサイトの安全なクロスオリジン設定
CORS は設定ミスと同じくらい誤解されています。これらのヘッダーが実際に何をするのか、ワイルドカード+資格情報の罠、そして API を正しく締める方法を解説します。
クロスオリジンリソース共有(CORS)は、多くのチームが初めてブラウザのセキュリティに出会う場所です——たいていは何かが壊れ、最速の検索結果が「全オリジンを許可すればいい」と言うから。その直し方は動き、静かに脆弱性になります。本記事では CORS が本当にしていることと、API をインターネット上の全サイトに開かずに設定する方法を解説します。
CORS が実際に制御するもの
CORS はサーバー側のアクセス制御ではありません。オリジン A で動く JavaScript がオリジン B の応答を*読む*ことを許すかどうかを決めるブラウザの仕組みです。curl やサーバー、ブラウザを無視するスクリプトからの直接リクエストは一切止めません。ブラウザクライアント向けのガードレールと捉え、本当の認可はいずれにせよサーバー側に置きましょう。
ワイルドカード+資格情報の罠
最も危険な単一の CORS 設定は、リクエストのオリジンを反射しつつ資格情報も許すことです。
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true受け取った Origin をそのまま返し、Allow-Credentials: true を設定すると、どのサイトもあなたのログイン済みユーザーとして認証付きリクエストを送り、応答を読めます。ブラウザがリテラルの * と資格情報の併用を禁じるのはこのためです——しかしオリジンの反射はその安全を回避し、穴を作り直します。
正しく制限的な設定
入ってくるオリジンを明示的な許可リストと照合し、その時だけ反射しましょう。
const allowed = new Set([
'https://app.example.com',
'https://admin.example.com',
]);
const origin = req.headers.get('origin');
if (origin && allowed.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}Vary: Origin ヘッダーは忘れやすく、重要です。これがないと、キャッシュがあるオリジンの許可ヘッダーを別のオリジンに返しかねません。
メソッドとヘッダーも絞る
Access-Control-Allow-Origin はポリシーの半分にすぎません。残りも引き締めましょう。
- そのエンドポイントが使うメソッドだけを許可する。例:
Access-Control-Allow-Methods: GET, POST。 - 想定するリクエストヘッダーだけを許可し、一括許可はしない。
- 妥当な
Access-Control-Max-Ageを設定し、プリフライトをキャッシュさせる。 Allow-Origin: *とAllow-Credentials: trueを決して組み合わせない。
ワイルドカードが実際に問題ないとき
真に公開・認証不要・読み取り専用の API——為替レートのフィードや公開ステータスのエンドポイント——は Access-Control-Allow-Origin: * を安全に使えます。資格情報がなく、漏れる私的情報がないからこそです。危険は常に * 的な開放と認証の組み合わせだけにあります。
実際のクロスオリジン環境でテストする
CORS は単純リクエストとプリフライトで挙動が異なるので、別オリジンのブラウザから両方をテストしましょう。curl(CORS を完全に無視)だけでは不十分です。スキャンは残りのセキュリティチェックとともに CORS 応答ヘッダーを自動で確認します。CORS の指摘が優先順位付きの修正リストにどう収まるかはウェブサイトのセキュリティ診断レポートの読み方をご覧ください。