• WordPressのnonceはwp_nonce_field()とcheck_admin_referer()を同じactionで対応させる

WordPressのnonceはwp_nonce_field()とcheck_admin_referer()を同じactionで対応させる

公開日: 執筆:佐藤

WordPressの管理画面で設定を保存するフォームには、wp_nonce_field()でnonceを入れます。受信側では、同じactionとfield名をcheck_admin_referer()へ渡し、保存や削除の前に検証します。

nonceの検証と、利用者の権限確認は別の処理です。nonceが正しくても、その利用者に設定を変更する権限があるとは限りません。フォームを作る側と受け取る側、操作要求と利用者権限を二組に分けます。

actionとfield名をフォームと受信側でそろえる

wp_nonce_field()の第一引数はaction、第二引数はhidden fieldの名前です。説明用に、設定保存のフォームへ次のように置きます。

<form method="post">
	<?php wp_nonce_field( 'satolab_save_options', 'satolab_options_nonce' ); ?>
	<!-- 設定項目と送信ボタン -->
</form>

出力されるhidden fieldには、satolab_options_nonceという名前と、satolab_save_optionsに対応して作られたnonceが入ります。wp_nonce_field()は、既定では参照元を示すhidden fieldも一緒に出力します。

受信側では、同じ二つの値を次の順で対応させます。

check_admin_referer( 'satolab_save_options', 'satolab_options_nonce' );
対応させる値 フォーム側 受信側
操作を表すaction wp_nonce_field()の第一引数 check_admin_referer()の第一引数
nonceを入れるfield名 wp_nonce_field()の第二引数 check_admin_referer()の第二引数

actionかfield名のどちらかが違えば、受信側は同じnonceとして検証できません。省略時の値に頼らず、操作の内容が分かるactionと、そのフォーム固有のfield名を明示します。

check_admin_referer()は書込みより先に実行する

WordPress 3.8のcheck_admin_referer()は、指定したfieldを要求から取り出し、wp_verify_nonce()へactionとともに渡します。検証に失敗すると確認画面を出して処理を終了します。

そのため、データベースの更新、ファイルの削除、外部への送信より先に検証します。

check_admin_referer( 'satolab_save_options', 'satolab_options_nonce' );

// nonceの検証後に設定を保存する
update_option( 'satolab_sample_option', $value );

保存後にnonceを調べても、無効な要求による変更は取り消せません。検証は記録を残すための後処理ではなく、変更処理へ進めるかを決める入口に置きます。

nonceと利用者の権限を別々に確認する

check_admin_referer()の処理には、current_user_can()による権限確認が含まれていません。nonceが有効という結果を、利用者がその操作を実行できるという結果へ置き換えないでください。

たとえば、サイト設定を変更する処理なら、必要な権限を先に決めて確認します。

if ( ! current_user_can( 'manage_options' ) ) {
	wp_die( 'この操作を実行する権限がありません。' );
}

check_admin_referer( 'satolab_save_options', 'satolab_options_nonce' );

update_option( 'satolab_sample_option', $value );

この例で、current_user_can()は利用者の権限を判定し、check_admin_referer()は送られたnonceを検証します。二つを通過したあとで、入力値の形式と許容範囲を検査し、保存します。nonceは入力値の内容まで正しくするものではありません。

wp_verify_nonce()を直接使う場合もactionを変えない

check_admin_referer()は、管理画面の要求を検証し、失敗時に処理を止めるところまでまとめています。検証結果に応じて独自の応答を返す必要がある場合は、要求からnonceを取り出し、wp_verify_nonce()を直接使えます。

$nonce = isset( $_POST['satolab_options_nonce'] )
	? wp_unslash( $_POST['satolab_options_nonce'] )
	: '';

if ( ! wp_verify_nonce( $nonce, 'satolab_save_options' ) ) {
	wp_die( '操作を確認できませんでした。' );
}

直接使う場合も、フォームで作成したactionと同じ文字列を渡します。wp_verify_nonce()は、有効な期間に作られたnonceなら1または2を返し、無効ならfalseを返します。戻り値を厳密な経過時間として扱わず、有効か無効かを処理の入口で判定します。

正常送信と欠落と権限不足を分けて試す

実装後は、一つの保存処理で結果を分けて確認します。

  1. 権限を持つ利用者が、表示されたフォームから送信する
  2. nonce fieldを欠いた要求を送信し、保存前に止まることを確認する
  3. actionまたはfield名を変え、保存前に止まることを確認する
  4. 必要な権限を持たない利用者では、有効なフォームを表示できても保存できないことを確認する
  5. 保存する値が不正なら、nonceと権限が正しくても入力検査で止まることを確認する

この順に試すと、nonce、権限、入力値、保存結果のどこで止まったかを分けられます。すべてを「保存できない」という一つの現象で調べるより、担当する検査が明確になります。

最初の一手は、既存フォーム一つについて、作成側のactionとfield名、受信側の二つの引数、権限確認、入力検査、保存処理を一枚に並べることです。同じactionが対応し、すべての検査が保存より前にあることを確認します。

戻る