結論
wp_verify_nonce()がfalseになる原因は、ほとんどが「action名の不一致」「作ったときと検証するときでユーザー(ログイン状態)が違う」「期限切れ」「ページキャッシュで古いnonceが配られている」「値が送られていない」のどれかです。- 成功時の戻り値は
1(12時間以内に発行)または2(12〜24時間前に発行)です。失敗時はfalseです。if ( wp_verify_nonce(...) )と書けば1も2も真になります。 - nonce はCSRF対策であり、権限チェックの代わりにはなりません。公式も、nonceを認証・認可・アクセス制御に頼らず
current_user_can()で保護するよう書いています。
原因・仕組み
nonce は、ユーザーID・action名・時間(12時間ごとの区切り)・セッショントークンなどから作る短い文字列です。公式ドキュメントによれば、時間情報は「Unixエポックから何回12時間が経ったか」で、これを2つ(現在と1つ前)まで許容するため、実際の有効期間は12〜24時間の間になります。
したがって、検証が通らない主な理由は次の表のとおりです。
| 原因 | 症状 | 対処 |
|---|---|---|
| action名が違う | 常に失敗 | 作成側と検証側で同じ文字列にする |
| ユーザーが違う | ログアウト後やログイン前に作った値は失敗 | ログイン後に作る。未ログイン用は別action |
| 期限切れ | 画面を長時間開きっぱなしにした後 | 再読み込みを促す。nonce_life で調整 |
| ページキャッシュ | 他人のnonceが配られ、失敗する | nonceを含むページをキャッシュ除外、またはAjaxで取得 |
| 値が届いていない | null や空文字 |
フィールド名(_wpnonce など)を確認 |
手順(サンプルコード)
フォームの例です。作成は wp_nonce_field()、検証は check_admin_referer() が定番です。
// フォーム側
<form method="post">
<?php wp_nonce_field( 'myplug_save', 'myplug_nonce' ); ?>
<input type="text" name="title" value="">
<button type="submit">保存</button>
</form>
// 保存側
add_action( 'admin_init', function () {
if ( ! isset( $_POST['myplug_nonce'] ) ) {
return;
}
// 1) nonce(CSRF対策)
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myplug_nonce'] ) ), 'myplug_save' ) ) {
wp_die( '不正なリクエストです。' );
}
// 2) 権限(認可)
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( '権限がありません。' );
}
// 3) サニタイズして保存
update_option( 'myplug_title', sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ) );
} );
Ajaxなら、JS側に wp_create_nonce( 'myplug_ajax' ) の値を渡し、PHP側で check_ajax_referer( 'myplug_ajax', 'nonce' ) を使います。
動作確認(検証環境と結果)
WordPress 7.1.2(ja)、PHP 8.2.12 で、php run.php nonce-verification-failed.php を実行しました(ログインユーザーは管理者ID 1)。
len=10
int(1) // 同じaction → 1
bool(false) // 違うaction
bool(false) // 空文字
bool(false) // null
logged-out verify: bool(false) // ログイン中に作ったnonceを、未ログインで検証
logged-out nonce verified as user 1: bool(false) // 未ログインで作ったnonceを、ユーザー1で検証
after nonce_life=2 and 3s sleep: bool(false) // nonce_life=2 秒にして3秒待つ
結論として、action違い・空値・ユーザー違い・期限切れは、すべて false でした。2(前の区切り)を返す場面は、この環境では再現していません(12時間待つ必要があるため、未検証です)。
wp_verify_nonce が false になる原因の注意点
$_POST['nonce']をそのまま渡すと、未設定でNoticeが出ます。issetで確認し、wp_unslashとsanitize_text_fieldを通してから検証します。wp_verify_nonce()がfalseのとき、そのまま処理を続けない(wp_die()かreturn)。「失敗したら警告だけ出して保存する」コードは悪い例です。- ページキャッシュ(CDN・キャッシュプラグイン)を使うサイトでは、フォームのnonceが古いまま配布され、一定時間後に全員が失敗します。フォーム部分をキャッシュ対象から外してください。
- 別のタブで再ログインすると、セッションが変わり、開きっぱなしのタブのnonceが通らなくなることがあります(仕組み上の説明で、今回は実測していません)。
wp_verify_nonce が false になる原因でよくあるミス
- action名の綴りが違う(
myplug_saveとmyplug-saveなど)。 wp_nonce_field( 'action', 'name' )の第2引数と、$_POST['name']が一致していない。admin-post.phpやAjaxで、nonceを検証せずに処理を始めている。- nonceが通ったから安全だと考え、
current_user_can()を書かない。
wp_verify_nonce が false になる原因のチェックリスト
- action名は固定文字列か、
delete-post_123のように対象IDを含めているか。 - 検証失敗時に処理を止めているか。
- 権限チェックを別に行っているか。
- キャッシュされるページに、ログイン用nonceが混ざっていないか。
wp_verify_nonce が false になる原因のFAQ(よくある質問)
Q. nonce の有効期限はちょうど24時間ですか?
A. 公式によると、12時間ごとの区切りを2つ許容するため、12〜24時間の間です。nonce_life フィルターで秒数を変えられます。
Q. 未ログインのフォームにも使えますか?
A. 使えますが、ユーザーIDが0として扱われるため、ログインの前後で値が変わります。
Q. check_admin_referer と wp_verify_nonce の違いは?
A. 前者は検証に失敗すると、自動で終了処理(wp_nonce_ays)まで行います。後者は結果を返すだけなので、自分で失敗時の処理を書きます。
筆者の見解(wp_verify_nonce が false になる原因)
nonce が失敗するときは、まず「誰が・いつ・何のactionで作ったか」を3点そろえて見るのが近道だと考えます。そのうえで、nonceを「合言葉」ではなく「このセッションが画面を開いた証拠」くらいに捉えると、権限チェックを別に書く理由も腑に落ちます。
wp_verify_nonce が false になる原因の関連項目
- wp_verify_nonce() の使い方|説明・引数・注意点
- wp_create_nonce() の使い方|説明・引数・注意点
- wp_nonce_field() の使い方|説明・引数・注意点
- wp_nonce_url() の使い方|説明・引数・注意点
- current_user_can() の使い方|説明・引数・注意点
