How to get a unique nonce for each Ajax request?

I’ve seen a couple of discussions about getting WordPress to regenerate a unique nonce for subsequent Ajax requests, but for the life of me I can’t actually get WordPress to do it– every time I request what I think should be a new nonce, I get the same nonce back from WordPress. I understand the concept of WP’s nonce_life and even setting it to something else, but that hasn’t helped me.

I don’t generate the nonce in the JS object in the header via localization- I do it on my display page. I can get my page to process the Ajax request, but when I request a new nonce from WP in the callback, I get the same nonce back, and I don’t know what I’m doing wrong… Ultimately I want to extend this so that there could be multiple items on the page, each with the ability to add/remove– so I need a solution that will allow multiple subsequent Ajax requests from one page.

Read More

(And I should say I’ve put all of this functionality into a plugin, so the front-end “display page” is actually a function included with the plugin…)

functions.php: localize, but I don’t create a nonce here

wp_localize_script('myjs', 'ajaxVars', array('ajaxurl' => 'admin-ajax.php')));

Calling JS:

$("#myelement").click(function(e) {
    e.preventDefault();
    post_id = $(this).data("data-post-id");
    user_id = $(this).data("data-user-id");
    nonce = $(this).data("data-nonce");
    $.ajax({
      type: "POST",
      dataType: "json",
      url: ajaxVars.ajaxurl,
      data: {
         action: "myfaves",
         post_id: post_id,
         user_id: user_id,
         nonce: nonce
      },
      success: function(response) {
         if(response.type == "success") {
            nonce = response.newNonce;
            ... other stuff
         }
      }
  });
});

Receiving PHP:

function myFaves() {
   $ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
   if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
      exit('Sorry!');

   // Get various POST vars and do some other stuff...

   // Prep JSON response & generate new, unique nonce
   $newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_' 
       . str_replace('.', '', gettimeofday(true)));
   $response['newNonce'] = $newNonce;

   // Also let the page process itself if there is no JS/Ajax capability
   } else {
      header("Location: " . $_SERVER["HTTP_REFERER"];
   }
   die();
}

Frontend PHP display function, amongst which is:

$nonce = wp_create_nonce('myplugin_myaction_nonce_' . $post->ID);
$link = admin_url('admin-ajax.php?action=myfaves&post_id=' . $post->ID
   . '&user_id=' . $user_ID
   . '&nonce=' . $nonce);

echo '<a id="myelement" data-post-id="' . $post->ID
   . '" data-user-id="' . $user_ID
   . '" data-nonce="' . $nonce
   . '" href="' . $link . '">My Link</a>';

At this point I’d be really grateful for any clues or pointers in getting WP to regenerate a unique nonce for each new Ajax request…


UPDATE: I’ve solved my problem. The code snippets above are valid, however I changed the $newNonce creation in the PHP callback to append a microseconds string to ensure that it is unique on subsequent Ajax requests.

Related posts

Leave a Reply

2 comments

  1. Here’s a very lengthy answer of my own question that goes beyond just addressing the question of generating unique nonces for subsequent Ajax requests. This is an “add to favorites” feature that was made generic for the purposes of the answer (my feature lets users add the post IDs of photo attachments to a list of favorites, but this could apply to a variety of other features that rely on Ajax). I coded this as a standalone plugin, and there are a few items missing– but this should be enough detail to provide the gist if you want to replicate the feature. It will work on an individual post/page, but it’ll also work in lists of posts (e.g. you can add/remove items to favorites inline via Ajax and each post will have its own unique nonce for each Ajax request). Keep in mind that there’s probably a more efficient and/or more elegant way to do this, and currently this works for Ajax only– I haven’t bothered yet to process non-Ajax $_POST data.

    scripts.php

    /**
    * Enqueue front-end jQuery
    */
    function enqueueFavoritesJS()
    {
        // Only show Favorites Ajax JS if user is logged in
        if (is_user_logged_in()) {
            wp_enqueue_script('favorites-js', MYPLUGIN_BASE_URL . 'js/favorites.js', array('jquery'));
            wp_localize_script('favorites-js', 'ajaxVars', array('ajaxurl' => admin_url('admin-ajax.php')));
        }
    }
    add_action('wp_enqueue_scripts', 'enqueueFavoritesJS');
    

    favorites.js (Lots of debug stuff that can be removed)

    $(document).ready(function()
    {
        // Toggle item in Favorites
        $(".faves-link").click(function(e) {
            // Prevent self eval of requests and use Ajax instead
            e.preventDefault();
            var $this = $(this);
            console.log("Starting click event...");
    
            // Fetch initial variables from the page
            post_id = $this.attr("data-post-id");
            user_id = $this.attr("data-user-id");
            the_toggle = $this.attr("data-toggle");
            ajax_nonce = $this.attr("data-nonce");
    
            console.log("data-post-id: " + post_id);
            console.log("data-user-id: " + user_id);
            console.log("data-toggle: " + the_toggle);
            console.log("data-nonce: " + ajax_nonce);
            console.log("Starting Ajax...");
    
            $.ajax({
                type: "POST",
                dataType: "json",
                url: ajaxVars.ajaxurl,
                data: {
                    // Send JSON back to PHP for eval
                    action : "myFavorites",
                    post_id: post_id,
                    user_id: user_id,
                    _ajax_nonce: ajax_nonce,
                    the_toggle: the_toggle
                },
                beforeSend: function() {
                    if (the_toggle == "y") {
                        $this.text("Removing from Favorites...");
                        console.log("Removing...");
                    } else {
                        $this.text("Adding to Favorites...");
                        console.log("Adding...");
                    }
                },
                success: function(response) {
                    // Process JSON sent from PHP
                    if(response.type == "success") {
                        console.log("Success!");
                        console.log("New nonce: " + response.newNonce);
                        console.log("New toggle: " + response.theToggle);
                        console.log("Message from PHP: " + response.message);
                        $this.text(response.message);
                        $this.attr("data-toggle", response.theToggle);
                        // Set new nonce
                        _ajax_nonce = response.newNonce;
                        console.log("_ajax_nonce is now: " + _ajax_nonce);
                    } else {
                        console.log("Failed!");
                        console.log("New nonce: " + response.newNonce);
                        console.log("Message from PHP: " + response.message);
                        $this.parent().html("<p>" + response.message + "</p>");
                        _ajax_nonce = response.newNonce;
                        console.log("_ajax_nonce is now: " + _ajax_nonce);
                    }
                },
                error: function(e, x, settings, exception) {
                    // Generic debugging
                    var errorMessage;
                    var statusErrorMap = {
                        '400' : "Server understood request but request content was invalid.",
                        '401' : "Unauthorized access.",
                        '403' : "Forbidden resource can't be accessed.",
                        '500' : "Internal Server Error",
                        '503' : "Service Unavailable"
                    };
                    if (x.status) {
                        errorMessage = statusErrorMap[x.status];
                        if (!errorMessage) {
                            errorMessage = "Unknown Error.";
                        } else if (exception == 'parsererror') {
                            errorMessage = "Error. Parsing JSON request failed.";
                        } else if (exception == 'timeout') {
                            errorMessage = "Request timed out.";
                        } else if (exception == 'abort') {
                            errorMessage = "Request was aborted by server.";
                        } else {
                            errorMessage = "Unknown Error.";
                        }
                        $this.parent().html(errorMessage);
                        console.log("Error message is: " + errorMessage);
                    } else {
                        console.log("ERROR!!");
                        console.log(e);
                    }
                }
            }); // Close $.ajax
        }); // End click event
    });
    

    Functions (front-end display & Ajax action)

    To output the Add/Remove Favorites link, simply call it on your page/post via:

    if (function_exists('myFavoritesLink') {
        myFavoritesLink($user_ID, $post->ID);
    }
    

    Front-end display function:

    function myFavoritesLink($user_ID, $postID)
    {
        global $user_ID;
        if (is_user_logged_in()) {
            // Set initial element toggle value & link text - udpated by callback
            $myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
            if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
                $toggle = "y";
                $linkText = "Remove from Favorites";
            } else {
                $toggle = "n";
                $linkText = "Add to Favorites";
            }
    
            // Create Ajax-only nonce for initial request only
            // New nonce returned in callback
            $ajaxNonce = wp_create_nonce('myplugin_myaction_' . $postID);
            echo '<p class="faves-action"><a class="faves-link"' 
                . ' data-post-id="' . $postID 
                . '" data-user-id="' . $user_ID  
                . '" data-toggle="' . $toggle 
                . '" data-nonce="' . $ajaxNonce 
                . '" href="#">' . $linkText . '</a></p>' . "n";
    
        } else {
            // User not logged in
            echo '<p>Sign in to use the Favorites feature.</p>' . "n";
        }
    
    }
    

    Ajax action function:

    /**
    * Toggle add/remove for Favorites
    */
    function toggleFavorites()
    {
        if (is_user_logged_in()) {
            // Verify nonce
            $ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
            if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
                exit('Sorry!');
            }
            // Process POST vars
            if (isset($_POST['post_id']) && is_numeric($_POST['post_id'])) {
                $postID = $_POST['post_id'];
            } else {
                return;
            }
            if (isset($_POST['user_id']) && is_numeric($_POST['user_id'])) {
                $userID = $_POST['user_id'];
            } else {
                return;
            }
            if (isset($_POST['the_toggle']) && ($_POST['the_toggle'] === "y" || $_POST['the_toggle'] === "n")) {
                $toggle = $_POST['the_toggle'];
            } else {
                return;
            }
    
            $myUserMeta = get_user_meta($userID, 'myMetadata', true);
    
            // Init myUserMeta array if it doesn't exist
            if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
                $myUserMeta['myMetadata'] = array();
            }
    
            // Toggle the item in the Favorites list
            if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
                // Remove item from Favorites list
                $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
                unset($myUserMeta['myMetadata'][$postID]);
                $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
                $myUserMeta['myMetadata'] = array_values($myUserMeta['myMetadata']);
                $newToggle = "n";
                $message = "Add to Favorites";
            } else {
                // Add item to Favorites list
                $myUserMeta['myMetadata'][] = $postID;
                $newToggle = "y";
                $message = "Remove from Favorites";
            }
    
            // Prep for the response
            // Nonce for next request - unique with microtime string appended
            $newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_' 
                . str_replace('.', '', gettimeofday(true)));
            $updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);
    
            // Response to jQuery
            if($updateUserMeta === false) {
                $response['type'] = "error";
                $response['theToggle'] = $toggle;
                $response['message'] = "Your Favorites could not be updated.";
                $response['newNonce'] = $newNonce;
            } else {
                $response['type'] = "success";
                $response['theToggle'] = $newToggle;
                $response['message'] = $message;
                $response['newNonce'] = $newNonce;
            }
    
            // Process with Ajax, otherwise process with self
            if (! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
                strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
                    $response = json_encode($response);
                    echo $response;
            } else {
                header("Location: " . $_SERVER["HTTP_REFERER"]);
            }
            exit();
        } // End is_user_logged_in()
    }
    add_action('wp_ajax_myFavorites', 'toggleFavorites');
    
  2. I really have to question the reasoning behind getting a new nonce for each ajax request. The original nonce will expire, but it can be used more than once until it does. Having the javascript receive it through ajax defeats the purpose, especially providing it on an error case. (The purpose of nonces being a little security for associating an action with a user within a time frame.)

    I’m not supposed to mention other answers, but I’m new and can’t comment above, so in regards to the posted “solution”, you are getting a new nonce every time but are not using it in the request. It would certainly be tricky to get the microseconds the same every time to match each new nonce created that way. The PHP code is verifying against the original nonce, and the javascript is supplying the original nonce…so it works (because it hasn’t expired yet).