wp_verify_nonce が false になる原因|nonce の作り方と検証

結論

  • 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 になる原因の関連項目

出典(一次情報)

タイトルとURLをコピーしました