2026年7月6日

2026年7月6日

WordPressでAJAXリクエストが403を返すときのnonce確認と対処法

admin-ajax.php への AJAX リクエストが 403 を返し、応答本文が -1 だけになっている場合は、nonce(ワンタイムトークン)の検証に失敗しています。原因は「nonce の期限切れ」「作成時と検証時のアクション名の食い違い」「キャッシュされた古い nonce」のどれかであることがほとんどです。この記事では、403 が本当に nonce によるものかを見分け、コード側・キャッシュ側のどちらを直すべきかを順番に確認する手順を解説します。

403 の原因は nonce とは限らない

まず押さえておきたいのは、AJAX の 403 には大きく2種類あることです。

  • WordPress の nonce 検証で拒否された: check_ajax_referer() が失敗すると、WordPress は 403 のステータスで -1 という本文だけを返します
  • WordPress に届く前に拒否された: サーバーの WAF(mod_security など)、セキュリティプラグイン、CDN のファイアウォールが、送信内容を攻撃と判断してブロックしたもの。この場合は HTML のエラーページが返ることが多い

この2つは対処がまったく違います。nonce ではない 403 に対してコードを直しても解決しないので、最初に見分けます。

ちなみに、ステータスが 400 で本文が 0 の場合は nonce ではなく、送信した action に対応するフック(wp_ajax_{action} / wp_ajax_nopriv_{action})が登録されていないことを示します。

手順1:開発者ツールで応答と送信内容を確認する

ブラウザの開発者ツール(F12)を開き、「ネットワーク」タブで AJAX を発生させる操作を行います。admin-ajax.php へのリクエストを選び、次を確認します。

  • ステータス: 403 か
  • レスポンス: -1 だけなら nonce の失敗。HTML のエラーページなら WAF やセキュリティプラグインを疑う
  • ペイロード(送信データ): action と nonce の値が送られているか。nonce の項目名がサーバー側で読む名前と一致しているか

REST API(/wp-json/ 配下)へのリクエストの場合は、403 の応答本文に含まれる code を見ます。rest_cookie_invalid_nonce なら、X-WP-Nonce ヘッダーで送った nonce が無効です。

手順2:作成側と検証側のアクション名を照合する

nonce は「どの操作のためのものか」を表すアクション名と結びついています。作成時と検証時でこの名前が1文字でも違うと、正しい nonce を送っても失敗します。もう1つよくあるのが、送信時の項目名と、検証時に読み取る項目名の食い違いです。check_ajax_referer() は第2引数を省略すると _ajax_nonce、次に _wpnonce という名前の値を探します。JavaScript 側で nonce という名前で送っているなら、第2引数に 'nonce' を指定する必要があります。

正しく組んだ場合の最小構成は次のとおりです。テーマの functions.php や独自プラグインに置く想定です。

add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_script(
		'my-ajax',
		get_stylesheet_directory_uri() . '/js/my-ajax.js',
		array(),
		'1.0.0',
		true
	);
	wp_localize_script( 'my-ajax', 'myAjax', array(
		'url'   => admin_url( 'admin-ajax.php' ),
		'nonce' => wp_create_nonce( 'my_ajax_action' ), // 作成時のアクション名
	) );
} );

add_action( 'wp_ajax_my_ajax_action', 'my_ajax_handler' );

function my_ajax_handler() {
	// 検証時のアクション名と、送信される項目名 'nonce' を指定
	check_ajax_referer( 'my_ajax_action', 'nonce' );

	if ( ! current_user_can( 'edit_posts' ) ) {
		wp_send_json_error( array( 'message' => '権限がありません' ), 403 );
	}

	$value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
	wp_send_json_success( array( 'value' => $value ) );
}

JavaScript 側では、action と nonce を同じ名前で送ります。

const body = new URLSearchParams( {
	action: 'my_ajax_action',
	nonce: myAjax.nonce,
	value: 'test',
} );

fetch( myAjax.url, {
	method: 'POST',
	credentials: 'same-origin',
	body,
} )
	.then( ( res ) => res.json() )
	.then( ( data ) => console.log( data ) );

credentials: 'same-origin' を付けるのは、ログイン Cookie を一緒に送るためです。nonce はユーザーごとに異なる値になるため、Cookie が送られずログアウト状態として扱われると、ログイン中に作った nonce は一致しません。

なお、nonce は「本人が意図した操作か」を確かめるもので、権限の確認ではありません。上の例のように current_user_can() での権限チェックと、受け取った値のサニタイズを組み合わせてください。

手順3:一時的にログを出して失敗理由を絞る

どの段階で失敗しているか分からない場合は、開発環境で検証結果をログに出すと判断しやすくなります。check_ajax_referer() の第3引数を false にすると、処理を止めずに結果を返します。

$result = check_ajax_referer( 'my_ajax_action', 'nonce', false );
error_log( 'nonce check: ' . var_export( $result, true ) . ' user: ' . get_current_user_id() );

wp-config.php で WP_DEBUG と WP_DEBUG_LOG を true にしておくと、wp-content/debug.log に記録されます。戻り値が 1 なら作成から12時間以内、2 なら12〜24時間、false なら無効です。ユーザー ID が 0 なら、サーバー側ではログアウト状態として扱われています。確認が終わったら、第3引数を外して本来の処理に戻し、WP_DEBUG も false にしてください。

手順4:期限切れとキャッシュを疑う

アクション名も項目名も正しいのに、時間が経つと失敗する、ログアウト状態の訪問者だけ失敗する、という場合は、古い nonce が使われています。

WordPress の nonce は既定で約24時間有効です(nonce_life フィルターで変更できます)。ページを長時間開きっぱなしにした後の操作や、ページキャッシュに保存された HTML の中の nonce は、期限を過ぎると無効になります。特に、キャッシュプラグインや CDN がページを長時間キャッシュしていると、全訪問者に同じ古い nonce が配られ、ある時刻から一斉に 403 が出るようになります。

対処の方針は次のとおりです。

  • AJAX を使うページをキャッシュ対象から外す、またはキャッシュの保持時間を短くする
  • nonce を HTML に埋め込まず、キャッシュされないエンドポイントから都度取得する
  • ログイン状態が途中で変わる画面では、ログイン・ログアウト後にページを再読み込みさせる。nonce はユーザー ID とログインセッションに結びついているため、状態が変わると以前の nonce は通らない

手順5:WAF・セキュリティプラグインを切り分ける

応答が -1 ではなく HTML のエラーページだった場合は、WordPress より手前でブロックされています。サーバーのエラーログや WAF のログに、該当時刻の拒否記録が無いかを確認します。レンタルサーバーの管理パネルで WAF を一時的に無効にして再現するか、セキュリティプラグインのブロック履歴を見ると原因を特定しやすくなります。

プラグインの影響かどうかは、Health Check & Troubleshooting のトラブルシューティングモードで、自分だけプラグインを無効にした状態で試すと判断できます。ただし AJAX 処理そのものがプラグインで実装されている場合は、そのプラグインだけを有効にして確認します。

nonce の有効期間や関連する関数の挙動は WordPress のバージョンによって変わる可能性があるため、作業時点の公式ドキュメントもあわせて確認してください(2026年10月現在)。

まとめ

  • 403 で本文が -1 なら nonce の失敗、HTML のエラーページなら WAF やセキュリティプラグインによるブロック
  • wp_create_nonce() と check_ajax_referer() のアクション名、送信と検証の項目名を照合する
  • Cookie が送られていないと、ログイン中に作った nonce は一致しない
  • 時間経過やログアウト状態でだけ失敗するなら、期限切れとページキャッシュを疑う
  • nonce は権限チェックの代わりにならないため、current_user_can() とサニタイズを併用する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲