How to Secure WordPress AJAX using Nonce

wp-ajax-nonce

Before you read this tutorial, please read earlier tutorial: WordPress AJAX for Beginners.

In this tutorial, I will explain several method in how to secure AJAX request using nonce.Probably some will argue that we don’t need to secure all AJAX request using nonce (e.g to query posts, because it’s a public data anyway). But I think it’s best to simply use nonce for ALL AJAX request because:

  1. Light weight.
  2. Super easy to implement.
  3. Safer. Prevent admin-ajax.php abuse.

In this tutorial, I will assume that you know what is WordPress nonce and how to use it.

There’s several method we can use to use nonce in AJAX:

1. Add Nonce In The DOM

We can add it in the DOM itself, and then get the nonce key using jQuery and send the data via AJAX. Here’s an example:

In the shortcode callback:

/**
 * Shortcode Callback
 * Just display empty div. The content will be added via AJAX.
 */
function my_wp_ajax_noob_john_cena_shortcode_callback(){

    /* Enqueue JS only if this shortcode loaded. */
    wp_enqueue_script( 'my-wp-ajax-noob-john-cena-script' );

    /* Create Nonce */
    $nonce = wp_create_nonce( 'john-cena-shortcode' );

    /* Output empty div. */
    return '<div id="john-cena" data-nonce="' . esc_attr( $nonce ) . '"></div>';
}

As you can see, we create a nonce using “john-cena-shortcode” keyword as nonce $action.

And then add this nonce key as data-nonce attributes in the DOM elements. So, the idea is to display the nonce in the DOM and then grab this nonce key using jQuery:

In our JavaScript:

$.ajax({
    type: "POST",                 // use $_POST request to submit data
    url: john_cena_ajax_url,      // URL to "wp-admin/admin-ajax.php"
    data: {
        action     : 'john_cena', // wp_ajax_*, wp_ajax_nopriv_*
        nonce_data : $( '#john-cena' ).data( 'nonce' ), // PHP: $_POST['nonce_data']
        first_name : 'John',      // PHP: $_POST['first_name']
        last_name  : 'Cena',      // PHP: $_POST['last_name']
    },
    success:function( data ) {
        $( '#john-cena' ).html( data );
    },
    error: function(){
        console.log(errorThrown); // error
    }
});

So, in the jQuery above, we submit our nonce data using $_POST['nonce_data'] and we get the data attributes using jQuery data:

nonce_data : $( '#john-cena' ).data( 'nonce' ),

Checking Nonce in AJAX Callback:

What we need to do now is to check and verify the nonce in our AJAX Callback. If it’s valid we can return the data requested, but if verification fail, stop the data.

We can do this using check_ajax_referer() function.

/**
 * Ajax Callback
 */
function my_wp_ajax_noob_john_cena_ajax_callback(){

    /* First, check nonce */
    check_ajax_referer( 'john-cena-shortcode', 'nonce_data' );

    /* Get request */
    $first_name = isset( $_POST['first_name'] ) ? $_POST['first_name'] : 'N/A';
    $last_name = isset( $_POST['last_name'] ) ? $_POST['last_name'] : 'N/A';
    ?>
    <p>Hello. Your first name is <?php echo strip_tags( $first_name ); ?>.</p>
    <p>And your last name is <?php echo strip_tags( $last_name ); ?>.</p>
    <?php
    wp_die(); // required. to end AJAX request.
}

That’s it, one line of code! #nice

i-got-this-shit

So the check_ajax_refferer() will simply kill the callback using die( -1 ). And it will effectively stop the process.

2. Add Nonce via Localize Script

Other popular method is using wp_localize_script() to add the nonce. This is the same function we use in previous tutorial in how to get the AJAX URL.

Add Nonce in Localize Script

In earlier tutorial we use localize script to only send single data, but we can also use it to send multiple data using array:

add_action( 'wp_enqueue_scripts', 'my_wp_ajax_noob_scripts' );

/**
 * Scripts
 */
function my_wp_ajax_noob_scripts(){

    /* Plugin DIR URL */
    $url = trailingslashit( plugin_dir_url( __FILE__ ) );

    /* JS + Localize */
    wp_register_script( 'my-wp-ajax-noob-john-cena-script', $url . "assets/script.js", array( 'jquery' ), '1.0.0', true );

    /* Localize Script Data */
    $ajax_data = array(
        'url'   => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'john-cena-script-nonce' ),
    );

    /* Send Data as JS var via Localize Script */
    wp_localize_script( 'my-wp-ajax-noob-john-cena-script', 'john_cena_ajax_data', $ajax_data  );
}

Get the nonce in jQuery:

$.ajax({
    type: "POST",                  // use $_POST request to submit data
    url: john_cena_ajax_data.url, // URL to "wp-admin/admin-ajax.php"
    data: {
        action     : 'john_cena', // wp_ajax_*, wp_ajax_nopriv_*
        nonce_ajax : john_cena_ajax_data.nonce,
        first_name : 'John',      // PHP: $_POST['first_name']
        last_name  : 'Cena',      // PHP: $_POST['last_name']
    },
    success:function( data ) {
        $( '#john-cena' ).html( data );
    },
    error: function(){
        console.log(errorThrown); // error
    }
});

So, this array of data in localize script will be loaded as js object, so it’s also super easy to access that data via jQuery:

url: john_cena_ajax_data.url,
nonce_ajax : john_cena_ajax_data.nonce,

Validate Nonce in AJAX Callback

It’s basically the same. Simply use check_ajax_refferer the same way as previous method. But, make sure you use the correct $action and request var.

check_ajax_referer( 'john-cena-script-nonce', 'nonce_ajax' );

Example Plugin

I’ve updated the example plugin to use both method. You only need to use one of them.

You can download the plugin here:

Download GitHub (check the branch)

Follow me on twitter @turtlepod to get update.

Comment below, let me know if I miss something OR if you want more explanation about this tutorial.

And share this tutorial if you think it’s useful 🙂

3 Comments

  1. John

    Hi David,

    what are your thoughts on using the same nonce multiple times?
    I mean, docs are saying nonce is a number used once, but you can use it multiple times in 12h or 24h period, right?

    What if I have let’s say, 2 different widgets, and the first one is displaying post titles and the second page titles. Each of those has load more button which will load more post/page titles through Ajax. I can use the same nonce for both, and it will work. But, should I do it that way, or I should create separate nonce for each widget? What do you think?

    Looking forward to your answer!

Comments are closed.