wp_verify_nonce return false despite correct parameter passed

The question:

I have created a form that have nonce

<form action="<?php echo get_rest_url(null, 'ilms_plugin/new_membership') ?>" method="post" class=' space-y-2 mt-2  p-2 rounded-md '>
        <?php wp_nonce_field( 'new_membership','test_nonce' ); ?>
        <h1 class="pt-2 text-xl">New membership</h1> 
        <div class='pr-2'>Membership name</div><input name="membership_name" type="text" id='shortname' class="w-full"/>
        <div class='pr-2'>Description</div><textarea name="description" id="" cols="30" rows="6" class="w-full"></textarea>
        <div class="pr-2">Availability</div> Day <input name="membership_period_day" type="number" id="membership_period_day" class="w-full" />
        <div class="pr-2">Price</div> $ HKD <input name="membership_price" type="number" id="membership_price" class="w-full" />
        <div><button class='bg-green-500 mt-2 p-2 rounded-md text-white'>Add</button></div>
        
    </form>

In my callback in another php file, I have the following:

function new_membership($data){
global $wpdb;
$id= 0;
$nonce = $data['test_nonce'];

if ( !wp_verify_nonce($nonce, 'new_membership') ) {
    var_dump("nonce rotten!");
    exit; // Get out of here, the nonce is rotten!
}
if (isset ($data['membership_name']) && isset($data['description'])){
    $sql = $wpdb->prepare( "INSERT INTO memberships (membership_name, membership_description, membership_period_day, membership_price) VALUES ( %s, %s, %d, %d)",
        $data['membership_name'], $data['description'], $data['membership_period_day'], $data['membership_price']);
    $wpdb->query($sql);
    $id = $wpdb->insert_id;
}

wp_redirect(get_admin_url()."?page=iMembership-page");
exit();
}

But the wp_verify_nonce() keep returning false. I dont know what is the reason and how to fix it.

Edit to reply comment:

to Anton: the value of test_nonce is “07213d197c”. On the form I set it like this

<?php wp_nonce_field( 'new_membership','test_nonce' ); ?>

to Sally CJ: yes, I call it using register_rest_route() in my custom_plugin.php

add_action('rest_api_init', function () {

register_rest_route( 'custom_plugin', 'new_membership',[
    'methods'  => 'POST',
    'callback' => 'new_membership',
]);
}

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

the wp_verify_nonce() keep returning false

If you were logged-in to your (WordPress) site when you used the form, then the above is normal. Here’s why so:

  • Your form submits to a custom REST API endpoint (at /wp-json/ilms_plugin/new_membership) and the default authentication method used by the REST API is cookie-based, i.e. it checks if a nonce with the action named wp_rest is set and that it’s valid (not yet expired), and if not (not set or it’s invalid/expired), then WordPress sets the current user to 0 which affects all nonce-related functions including but not limited to wp_verify_nonce().

  • In simple words, it means functions like wp_verify_nonce() will return false because nonces are based on the current user, so if the user is logged-in to your site (when filling in the form), but not the REST API (when the form submission/data is processed), then wp_verify_nonce() will return false because the user ID becomes 0 in the REST API.

Check out the REST API handbook for more details about the authentication: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/

How to fix the issue

Just add wp_nonce_field( 'wp_rest', '_wpnonce', false ); somewhere in your form, e.g. right after the opening <form> tag: (Note that I escaped the URL using esc_url())

<form action="<?php echo esc_url( get_rest_url( null, 'ilms_plugin/new_membership' ) ); ?>" ...>
    <?php wp_nonce_field( 'wp_rest', '_wpnonce', false ); ?>
    ...

Additional Notes

  1. Instead of get_rest_url( null, 'ilms_plugin/new_membership' ), you could use rest_url( 'ilms_plugin/new_membership' ) which uses rest_url() and is shorter.. 🙂

  2. Instead of using $wpdb->query(), I would use $wpdb->insert() to insert the data to the database.

  3. A REST API endpoint should always set a permission_callback and you should also use the args argument to define the parameters for the endpoint like the test_nonce in your case. Nonetheless, refer to Adding Custom Endpoints in the REST API handbook for further details.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment