To avoid Cross-Site Request Forgery (CSRF) attacks, WordPress has two functions for plugin developers: wp_verify_nonce and check_ajax_referer. If you don’t use at least one of these functions, your plugin could be vulnerable to outside attackers, which means your plugin could be the source of a severe data breach or site takeover. As a plugin developer, you should take your customers’ security seriously. CSRF isn’t the only vulnerability in WordPress, but we’ll discuss how to ensure that a public attacker can’t execute functions without authorization and harm your customer sites.

The key difference between check_ajax_referer and wp_verify_nonce is that the former returns a 403 (Forbidden) error and stops the application, but the latter validates and lets you decide what happens next. Because check_ajax_referer sends a generic error message, most plugin developers opt to use the wp_verify_nonce function instead. This function lets you create custom error messages and customize what happens when the user can’t execute the action. If you need to validate user permissions (usually happens in addition to validating a nonce), check out my write-up on working with the WordPress current_user_can function.

How to Avoid the Dreaded Potential CSRF Attack Detected Warning

WordPress security plugins work hard to protect site owners from outside attacks, but CSRF attacks are difficult to detect. CSRF attacks are valid requests from the server, so it takes more than just identifying malformed requests. Plugins like Wordfence are great at stopping SQL injection, brute-force attacks, and malformed Cross-Site Scripting (XSS) attacks. Most WordPress CSRF vulnerabilities are exploited using scripts, so the entire process is automated, which means a single site could be under attack by multiple attackers at once.

CSRF vulnerabilities in WordPress usually come from ajax handlers set up in plugin code. The plugin author uses WordPress hooks to handle user actions (like submitting a form or adding a user), and these ajax handlers can be called using a URL. The ajax hook function is called using a URL like below:

mysite.com/?action=ajax_handler

If you want to see a detailed description of an ajax handler CSRF security bug, check out my write-up on CVE-2025-8489. The security bug in King Addon for Elementor allowed a low-privilege user like a contributor to add an administrator account, allowing the attacker to then take over the entire WordPress site.

WordPress CSRF Token, or Nonce (Number Used Once)

CSRF tokens aren’t specific to WordPress. They are randomly generated values used to ensure that the request to perform an action (usually a form function) is from a valid user, and not from a script. For example, suppose that you have a form where users can update their information. The user ID is included in the request so that you know which user to update. You use a CSRF token to ensure that the user making the request is actually the user and not a random outside attacker. 

A CSRF token can be used in adding or deleting records too. For example, you don’t want an attacker to use a script to create thousands of users. A CSRF token ensures that the request to create a user comes from the form on your site. You create the token and then validate it before performing an action.

WordPress uses a CSRF token called a nonce (number used once). This nonce is created by the plugin developer and tied to an action. The developer then verifies the nonce before performing an ajax action. The nonce is sent via a POST or GET server request.

You create the nonce using the following code:

$nonce = wp_create_nonce(“edit_profile”);

This is an example of assigning a nonce to a PHP variable, but it can also be used in JavaScript:

<?php
   $nonce = wp_create_nonce( "edit_profile" );
?>

<script type="text/javascript">
     jQuery(document).ready(function($){
     var data = {
          action: 'ajax_edit_profile',
          security: '<?php echo $nonce; ?>'
     };
     $.post(ajaxurl, data, function(response) {
          alert("Response: " + response);
     });
});
</script>

In the above code, the nonce is created and then passed to the “security” variable in the jQuery JavaScript function. You would place this nonce in the plugin page where the user loads your form. The action value defines the ajax function to call in WordPress. This function (in this case ajax_edit_profile) should then include either check_ajax_referer or wp_verify_nonce in its code to ensure that only this jQuery function can make the ajax call. The next two sections show you how to validate the CSRF token (nonce) using either check_ajax_referer or wp_verify_nonce.

check_ajax_referer Example to Prevent CSRF

Let’s say that you have a form that gives users the ability to delete images they upload to your WordPress server. You don’t want anyone to be able to delete images except the owner of the images or an administrator using the form on your site. Without check_ajax_referer to check validation, any attacker on the internet could iterate over user ID values and delete images tied to a user ID.

The check_ajax_referer function ensures that calls to the ajax function have a nonce (number used once). The nonce is a defined value added to your plugin form and stored in WordPress. The nonce is tied to an action, like “delete_image” so that a user can delete an image only if the correct nonce is included in the server request.

Let’s use the nonce for editing profile data created in the previous section. We want to validate the nonce when calling the ajax_edit_profile ajax handler defined in the previous section. Here is a code snippet of what would be the ajax handler:

function ajax_edit_profile() {
   check_ajax_referer( 'edit_profile', 'security' );
       //do a thing if the nonce is valid
}

It should be noted that check_ajax_referer calls the wp_verify_nonce function within its own code. The difference is that if the nonce fails validation, the WordPress site returns a 403, which can be confusing for users. If you don’t care or want to give a generic error, then know that the check_ajax_referer function code calls the die function, which stops all execution of the PHP script.

The check_ajax_referer function takes two parameters. The first one is the action linked to the nonce when you created it. We used wp_create_nonce( “edit_profile” ), so the action tied to the nonce is “edit_profile.” The “security” parameter is the GET or POST variable that contains the nonce, in this jQuery example it’s named “security.”

Having the code return a 403 isn’t usually optimal, so next we’ll show you how to use wp_verify_nonce for a more customized approach.

How to Use wp_verify_nonce 

The check_ajax_referer is great for a nuclear solution, but wp_verify_nonce is much more popular since it does not call the die PHP feature and lets you determine what happens next if the nonce check fails. We can use the same JavaScript jQuery code and apply it to this wp_verify_nonce example code below:

function ajax_edit_profile() {

   if(!wp_verify_nonce( $_REQUEST[‘security’], 'edit_profile' ) {
      //invalid, show error to user
      //do something else
   }  
}

In the code above, the statement says that “if the wp_verify_nonce function returns false, then show an error and do something else.” Don’t forget that you still need to validate user authorization before allowing the function to execute, so both security features are usually present in a WordPress ajax hook function.

If all checks out, your plugin code can continue execution. In this example, the logical next step is to make changes to the user profile. This step usually involves the database, so make sure you secure your code from SQL injection!

Subscribe for More Fun WordPress Security Content

I spend my days checking out the latest vulnerabilities to help new devs understand the importance of coding secure WordPress plugins. Sign up for my newsletter to get alerted to more WordPress vulnerabilities and tutorials.

Join Our Newsletter
get weekly access to the latest hacks, tricks, and updates