2026年6月5日
2026年6月5日
CORS の動作確認手順|curlとブラウザで許可・拒否・プリフライトを確かめる方法
CORS(オリジン間リソース共有)の設定が意図どおりかを確かめるには、「curl でサーバーが返すヘッダーを確認する」「ブラウザの開発者ツールで実際の判定を見る」「許可したいオリジンと許可したくないオリジンの両方で試す」の3点を組み合わせます。CORS はサーバーがヘッダーを返し、ブラウザがそれを見て通すかどうかを決める仕組みなので、どちらか一方だけの確認では見落としが出ます。この記事では、設定を変えた後や公開前に行う確認の手順を、WordPress の REST API を例に説明します。エラーが出てからの原因調査ではなく、設定が正しいことを確かめる作業に絞った内容です。
確認の前に押さえておくこと
CORS の確認でつまずきやすいのは、次の2点です。
- curl では CORS エラーは起きない: CORS はブラウザが行う制限です。curl やサーバー間の通信は、ヘッダーが何であってもレスポンスを受け取れます。curl で分かるのは「サーバーがどのヘッダーを返しているか」までで、ブラウザが通すかどうかは開発者ツールで確認します。
- リクエストには2種類ある: GET や、特定の形式の POST だけを使うリクエストは、そのまま送られます。独自ヘッダー(
X-WP-NonceやAuthorization)を付けたり、Content-Type: application/jsonで送ったり、PUT・DELETE を使ったりすると、ブラウザは先に OPTIONS メソッドで「プリフライト」と呼ばれる確認のリクエストを送ります。プリフライトが通らないと、本来のリクエストは送られません。
確認を始める前に、次の情報を書き出しておくと作業が進めやすくなります。
- 呼び出し元のオリジン(例:
https://app.example.net)。スキーム・ホスト名・ポートの組み合わせで1つのオリジンです - 呼び出し先のURL(例:
https://example.com/wp-json/wp/v2/posts) - 使うメソッドと独自ヘッダー
- Cookie などの資格情報を送るかどうか
手順1:curl で単純なリクエストのヘッダーを見る
Origin ヘッダーを付けてリクエストし、レスポンスヘッダーを確認します。
curl -s -D - -o /dev/null \
-H "Origin: https://app.example.net" \
https://example.com/wp-json/wp/v2/posts
確認する項目は次のとおりです。
Access-Control-Allow-Originが、送ったオリジンと同じ値(または*)になっているか- 資格情報を使う場合、
Access-Control-Allow-Credentials: trueがあり、Access-Control-Allow-Originが*ではなく具体的なオリジンになっているか - オリジンによって返す値を変えている場合、
Vary: Originが付いているか
Access-Control-Allow-Origin が2行出ている、または https://a.example, https://b.example のように複数の値が入っている場合、ブラウザはエラーにします。Webサーバーの設定とアプリケーション(WordPressやプラグイン)の両方でヘッダーを付けていると起こりやすい問題です。
手順2:curl でプリフライトを再現する
プリフライトが必要なリクエストを使う場合は、OPTIONS のリクエストを組み立てて確認します。
curl -s -D - -o /dev/null -X OPTIONS \
-H "Origin: https://app.example.net" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type, x-wp-nonce" \
https://example.com/wp-json/wp/v2/posts
見るべき点は次のとおりです。
- ステータスコード: 200 または 204 など、成功を表すコードであること。401・403・404・405 が返る場合は、認証、WAF、Webサーバーの設定などが OPTIONS を止めています
- リダイレクトしていないか: プリフライトへのレスポンスが 301 や 302 だと、ブラウザは失敗として扱います。
http://からhttps://への転送や、末尾スラッシュの付け外しによる転送が典型です。呼び出すURLは転送後の最終的な形で書きます - **
Access-Control-Allow-Methods**: 使うメソッドが含まれているか - **
Access-Control-Allow-Headers**: 送る独自ヘッダーが含まれているか - **
Access-Control-Max-Age**: プリフライトの結果をブラウザがキャッシュする時間。設定変更の確認中に古い結果が使われる原因になるため、確認中はこの値を意識します
手順3:許可しないオリジンでも試す
許可したオリジンで通ることだけでなく、許可していないオリジンで許可のヘッダーが返らないことも確認します。
curl -s -D - -o /dev/null \
-H "Origin: https://not-allowed.example.org" \
https://example.com/wp-json/wp/v2/posts | grep -i '^access-control'
何も表示されないか、許可したオリジンとは違う値が返るのが期待どおりの動作です。送ったオリジンをそのまま返す設定になっていると、どのオリジンからでも読めてしまいます。特に Access-Control-Allow-Credentials: true と組み合わせている場合は、ログイン中の利用者の情報を他のサイトから読まれる危険につながるため、許可するオリジンを一覧で限定します。
手順4:ブラウザの開発者ツールで確認する
サーバーのヘッダーが正しく見えても、最終的な判定はブラウザで確認します。呼び出し元のオリジンのページ(ステージングでもかまいません)を開き、開発者ツールのコンソールで実際にリクエストを送ります。
fetch('https://example.com/wp-json/wp/v2/posts?per_page=1')
.then((res) => console.log(res.status, res.headers.get('content-type')))
.catch((err) => console.error('失敗:', err));
結果は次のように読みます。
- 成功した場合: ステータスコードが表示されます。ネットワークタブでリクエストを選び、レスポンスヘッダーに
Access-Control-Allow-Originが付いていることを確認します - CORS で止まった場合: コンソールに CORS に関するエラーが表示され、
catchに入ります。JavaScript からはステータスコードもヘッダーも読めないため、原因はエラーメッセージとネットワークタブで調べます - プリフライトの確認: ネットワークタブには OPTIONS のリクエストが別の行として表示されます(ブラウザによってはフィルターの設定で表示されます)。本来のリクエストより先に送られ、成功しているかを確認します
資格情報を送る場合は、fetch に credentials: 'include' を付けて試します。資格情報の有無でサーバーに求められる条件が変わるため、本番と同じ条件で確認することが大切です。
確認には、ブラウザの拡張機能などで CORS の制限を無効にした状態を使わないようにします。利用者のブラウザと条件が変わり、確認の意味がなくなります。
手順5:WordPress 特有の確認ポイント
WordPress の REST API は、コアの関数 rest_send_cors_headers() で CORS のヘッダーを付けています。この関数は rest_pre_serve_request フィルターに登録されていて、既定ではリクエストの Origin をそのまま Access-Control-Allow-Origin に返し、資格情報も許可する動作になっています(細部はバージョンで変わる可能性があるため、利用中のバージョンのコードで確認してください)。Cookie 認証での書き込みには nonce が必要なため、それだけで他サイトから書き込まれるわけではありませんが、許可するオリジンを限定したい場合は、この動作を置き換えます。
<?php
// mu-plugins/limit-rest-cors.php など。テーマの更新で消えない場所に置く
add_action( 'rest_api_init', function () {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function ( $served ) {
$allowed = array( 'https://app.example.net' );
$origin = get_http_origin();
if ( $origin && in_array( $origin, $allowed, true ) ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
header( 'Access-Control-Allow-Headers: Content-Type, X-WP-Nonce' );
header( 'Access-Control-Allow-Credentials: true' );
}
header( 'Vary: Origin', false );
return $served;
} );
}, 15 );
このような変更は、ステージング環境で手順1〜4を一通り試してから本番に反映します。あわせて次の点も確認します。
- ヘッダーの付与場所が1か所か: Webサーバー(nginx の
add_headerや Apache のHeader set)とWordPressの両方で付けていないか。手順1でヘッダーが重複していないことを確かめます - キャッシュとCDN: ページキャッシュやCDNが、あるオリジン向けのレスポンスをキャッシュして別のオリジンに返していないか。
Vary: Originを尊重するか、CORS を使うパスをキャッシュの対象から外すかを、使っているサービスの説明で確認します - フォントや画像: Webフォントを別のドメインやCDNから配信している場合も CORS の対象になります。フォントファイルのURLにも手順1を行います
ブラウザの CORS に関する挙動や開発者ツールの表示は、ブラウザのバージョンで変わることがあります(2026年10月現在)。
まとめ
- curl はサーバーが返すヘッダーの確認、ブラウザの開発者ツールは実際の判定の確認に使い分ける
- 単純なリクエストとプリフライト(OPTIONS)の両方を試し、ステータスコードとリダイレクトの有無を見る
- 許可しないオリジンで許可のヘッダーが返らないことも確認する
- ヘッダーの重複、
Vary: Originの有無、キャッシュの影響を見落とさない - WordPress では
rest_send_cors_headers()の既定の動作を理解し、変更はmu-pluginなどに置いてステージングで確認する