2026年5月29日

2026年5月29日

WordPressのNonceでセキュリティを強化する方法|フォーム・リンク・Ajax・REST APIでの使い方と限界

WordPressの nonce は、「このリクエストは、本人がこのサイトの画面から意図して送ったものか」を確かめるための、一定時間だけ有効なトークンで、主にクロスサイトリクエストフォージェリ(CSRF)を防ぐために使います。フォームやリンク、Ajax、REST API で値を変更する処理には、nonce の検証と権限チェックをセットで入れるのが基本です。この記事では、nonce の仕組みと有効期間、場面ごとの発行と検証のコード、nonce だけでは防げないこと、キャッシュとの相性や「リンクの有効期限が切れています」と出たときの考え方までを解説します。

nonce が防ぐもの、防がないもの

CSRF は、ログイン中の管理者が攻撃者の用意したページを開いたときに、そのブラウザから自分のサイトへ「設定を変更する」「記事を削除する」といったリクエストを勝手に送らせる攻撃です。ブラウザはログイン中のCookieを自動で付けるため、サーバー側はCookieだけでは本人の操作か区別できません。

nonce は、サイトの画面を表示したときにだけ埋め込まれる値です。攻撃者のページからはこの値を知ることができないので、nonce が正しいリクエストは「自サイトの画面から送られたもの」と判断できます。

一方で、nonce は次のことを保証しません。

  • 権限:nonce が正しくても、そのユーザーに操作する権限があるとは限りません。権限の確認は current_user_can() で別に行います。
  • 入力値の安全性:送られた値の検証・サニタイズ、出力時のエスケープは別途必要です。
  • ログインしていない利用者の識別:後述のとおり、未ログインの利用者には全員に同じ nonce が発行されます。

つまり nonce は「意図の確認」で、「誰が・何をしてよいか」は権限チェックが担います。両方そろって初めて安全な処理になります。

WordPress の nonce の仕組みと有効期間

WordPress の nonce は、名前のとおりの「一度きりの番号」ではありません。アクション名・ユーザーID・ログインセッションのトークン・時間帯から計算されるハッシュで、有効期間内なら何度でも検証に通ります。

  • 既定の有効期間は 24 時間で、12 時間ごとの2つの区切りで管理されます(2026年10月現在)。nonce_life フィルターで変更できます
  • wp_verify_nonce() は、前半の12時間に作られたものなら 1、後半なら 2、無効なら false を返します
  • ログアウトするとセッションが変わるため、それ以前に発行された nonce は無効になります

アクション名には、'delete_post_' . $post_id のように対象を含めると、別の投稿への操作に流用されるのを防げます。

フォームで使う:wp_nonce_field と check_admin_referer

管理画面に独自の設定フォームを作り、admin-post.php で受け取る例です。

<?php
// フォームの表示側
function myplugin_render_form() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}
	?>
	<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
		<input type="hidden" name="action" value="myplugin_save">
		<?php wp_nonce_field( 'myplugin_save_settings', 'myplugin_nonce' ); ?>
		<input type="text" name="myplugin_label" value="<?php echo esc_attr( get_option( 'myplugin_label', '' ) ); ?>">
		<?php submit_button(); ?>
	</form>
	<?php
}

// 受け取り側
add_action( 'admin_post_myplugin_save', function () {
	check_admin_referer( 'myplugin_save_settings', 'myplugin_nonce' ); // 失敗すると処理を停止
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_die( esc_html__( '権限がありません。', 'myplugin' ), 403 );
	}
	$label = isset( $_POST['myplugin_label'] )
		? sanitize_text_field( wp_unslash( $_POST['myplugin_label'] ) )
		: '';
	update_option( 'myplugin_label', $label );
	wp_safe_redirect( add_query_arg( 'updated', '1', wp_get_referer() ?: admin_url() ) );
	exit;
} );

wp_nonce_field() は hidden の入力欄を出力し、check_admin_referer() はその値を検証して、不正なら処理を止めます。第1引数のアクション名と第2引数の項目名は、発行側と検証側で一致させます。

設定画面を Settings API(register_setting() と settings_fields())で作る場合は、settings_fields() が nonce を自動で出力し、options.php 側で検証されます。自分で nonce を書く必要はありませんが、register_setting() で sanitize_callback を指定して入力値をサニタイズするのは自分の役目です。

投稿の保存時に使う:メタボックス

save_post は自動保存や他のプラグインからの更新でも実行されるため、check_admin_referer() のように処理を止める関数は使わず、wp_verify_nonce() で判定して抜けます。

<?php
add_action( 'save_post', function ( $post_id ) {
	if ( ! isset( $_POST['myplugin_meta_nonce'] ) ) {
		return;
	}
	$nonce = sanitize_text_field( wp_unslash( $_POST['myplugin_meta_nonce'] ) );
	if ( ! wp_verify_nonce( $nonce, 'myplugin_save_meta_' . $post_id ) ) {
		return;
	}
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}
	if ( isset( $_POST['myplugin_subtitle'] ) ) {
		update_post_meta( $post_id, 'myplugin_subtitle', sanitize_text_field( wp_unslash( $_POST['myplugin_subtitle'] ) ) );
	}
} );

メタボックスの表示側では wp_nonce_field( 'myplugin_save_meta_' . $post->ID, 'myplugin_meta_nonce' ) を出力しておきます。

リンクで使う:wp_nonce_url

「削除する」「承認する」のように、クリック1つで状態が変わるリンクにも nonce を付けます。

<?php
$url = wp_nonce_url(
	admin_url( 'admin-post.php?action=myplugin_delete&item=' . absint( $item_id ) ),
	'myplugin_delete_' . absint( $item_id )
);
echo '<a href="' . esc_url( $url ) . '">' . esc_html__( '削除', 'myplugin' ) . '</a>';

受け取り側では check_admin_referer( 'myplugin_delete_' . $item_id ) で検証します。ただし、削除のような重要な操作は、本来 GET ではなく POST で受けるほうが安全です。リンク形式にする場合も、確認画面を挟むと誤操作を減らせます。

Ajax と REST API で使う

admin-ajax.php を使う Ajax では、JS に nonce を渡し、受け取り側で check_ajax_referer() を使います。

<?php
add_action( 'wp_enqueue_scripts', function () {
	wp_enqueue_script( 'myplugin-like', plugins_url( 'like.js', __FILE__ ), array(), '1.0.0', array( 'in_footer' => true ) );
	wp_add_inline_script( 'myplugin-like', 'const mypluginLike = ' . wp_json_encode( array(
		'url'   => admin_url( 'admin-ajax.php' ),
		'nonce' => wp_create_nonce( 'myplugin_like' ),
	) ) . ';', 'before' );
} );

add_action( 'wp_ajax_myplugin_like', function () {
	check_ajax_referer( 'myplugin_like', 'nonce' ); // 失敗すると -1 を返して終了
	if ( ! current_user_can( 'read' ) ) {
		wp_send_json_error( null, 403 );
	}
	$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
	// … いいねを記録する処理 …
	wp_send_json_success( array( 'post_id' => $post_id ) );
} );

JS からは fetch() で action=myplugin_like、nonce、post_id を POST します。

REST API をログイン中のユーザーのCookieで呼ぶ場合は、wp_create_nonce( 'wp_rest' ) で作った値を X-WP-Nonce ヘッダーで送ります。この nonce が無いと、WordPressはCookieがあってもリクエストを未ログイン扱いにします。ブロックエディターなど wp-api-fetch を使う画面では、この処理が自動で行われます。独自のエンドポイントでは、nonce とは別に register_rest_route() の permission_callback で権限を確認してください。

つまずきやすい点

ページキャッシュと nonce

nonce を含むページをページキャッシュやCDNが長時間保存すると、有効期間を過ぎた古い nonce が配られ、送信のたびに失敗します。ログイン中のユーザーには通常キャッシュを返さない設定が多いものの、未ログインでも使うフォームでは問題になります。対策としては、そのページをキャッシュの対象から外す、送信直前に Ajax や REST API で nonce を取り直す、キャッシュの保持時間を nonce の有効期間より十分短くする、といった方法があります。

未ログインの利用者では効果が限られる

未ログインの利用者はユーザーIDが 0 として扱われ、同じ時間帯なら全員に同じ nonce が発行されます。お問い合わせフォームのような公開フォームでは、nonce だけに頼らず、ハニーポットや送信回数の制限、スパム対策プラグインを組み合わせます。

「リンクの有効期限が切れています」と表示される

check_admin_referer() が失敗すると、この趣旨のメッセージで処理が止まります(表示される文言はバージョンや言語設定で異なることがあります)。原因として多いのは、画面を長時間開いたままにしていた、途中でログインし直した、キャッシュが古い nonce を返している、アクション名や項目名が発行側と検証側で違う、の順です。自作コードなら、まず名前の一致を確認します。

まとめ

  • nonce は CSRF を防ぐための「意図の確認」で、権限チェック(current_user_can())・サニタイズ・エスケープの代わりにはならない
  • 既定では24時間程度有効で、期間内なら何度でも通る。アクション名に対象のIDを含めると流用を防げる
  • フォームは wp_nonce_field() と check_admin_referer()、保存処理は wp_verify_nonce()、リンクは wp_nonce_url()、Ajax は check_ajax_referer() を使う
  • REST API のCookie認証では wp_rest の nonce を X-WP-Nonce ヘッダーで送り、permission_callback で権限を確認する
  • ページキャッシュや未ログインの利用者では nonce の効果が変わるため、キャッシュ設定と他の対策を組み合わせる

お気軽にご相談ください

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