Back to Blog
Lesson 24 of the Intermediate WordPress Plugins: REST API & React Admin course
WordPressJune 26, 20264 min read

Implementing Nonce Verification for WordPress REST API Security

Learn to secure your WordPress REST API against CSRF attacks. Master generating nonces, passing them via headers, and verifying them in your API endpoints.

WordPressREST APISecurityNoncesCSRFReactPHPplugin-development

Previously in this course, we explored Implementing REST API Permission Callbacks for Secure Plugins to ensure only authorized users could access our endpoints. While permission callbacks handle who can access a resource, they don't inherently prevent Cross-Site Request Forgery (CSRF).

CSRF is an attack where a malicious site tricks an authenticated user into performing unwanted actions on your site. Since the browser automatically includes cookies with requests, the REST API might trust the request if it only checks for authentication. This lesson adds a layer of defense: Nonce Verification.

Understanding Nonces and CSRF Protection

A "nonce" (number used once) is a unique, time-sensitive cryptographic token. In WordPress, it acts as a digital handshake. By requiring a nonce for any state-changing request (POST, PUT, DELETE), we verify that the request originated from our own admin dashboard, not an external, unauthorized source.

1. Generating Nonces in PHP

We don't want to hardcode tokens. Instead, we generate them server-side and pass them to our JavaScript application. In your main plugin file or wherever you localize your scripts, use wp_create_nonce():

PHP
#6A9955">// In your PHP initialization
wp_localize_script( 'my-plugin-script', 'wpApiSettings', array(
    'root'  => esc_url_raw( rest_url() ),
    'nonce' => wp_create_nonce( 'wp_rest' ), #6A9955">// The standard WordPress REST nonce
) );

The string 'wp_rest' is the standard action name for the WordPress REST API. By using this, you align your plugin with core security expectations.

2. Passing Nonces via Headers

When using @wordpress/api-fetch, you don't need to manually append the nonce to every URL or request body. Instead, you can configure the global apiFetch to include the nonce in the X-WP-Nonce header for every request.

In your entry-level JavaScript file (e.g., index.js):

JAVASCRIPT
import apiFetch from CE9178">'@wordpress/api-fetch';

// Set the nonce header globally
apiFetch.setFetchHandler( async ( options ) => {
    const headers = {
        CE9178">'X-WP-Nonce': window.wpApiSettings.nonce,
        ...options.headers,
    };

    return await apiFetch( { ...options, headers } );
} );

By setting this handler, you ensure every call made via apiFetch carries the security token required by the server.

3. Verification in REST Callbacks

Now that the client is sending the header, we must verify it in our endpoint. WordPress handles this automatically if you pass the permission_callback correctly. However, to be explicit or to perform custom checks, you can use wp_verify_nonce():

PHP
register_rest_route( 'kb/v1', '/update', array(
    'methods'  => 'POST',
    'callback' => 'kb_update_post',
    'permission_callback' => function( $request ) {
        #6A9955">// WordPress core automatically checks the X-WP-Nonce header 
        #6A9955">// if you return current_user_can() here.
        return current_user_can( 'edit_posts' );
    },
) );

If you ever need to verify a nonce manually inside the callback, you would do it like this:

PHP
if ( ! wp_verify_nonce( $request->get_header( 'x-wp-nonce' ), 'wp_rest' ) ) {
    return new WP_Error( 'rest_forbidden', 'Invalid nonce.', array( 'status' => 403 ) );
}

Hands-on Exercise

  1. Locate your existing wp_localize_script call in the plugin's PHP file.
  2. Add the nonce key to the localized object using wp_create_nonce('wp_rest').
  3. Update your JavaScript service layer to ensure apiFetch is configured with the X-WP-Nonce header.
  4. Test the implementation by opening your browser's Network tab in the Developer Tools. Submit an update from your Knowledge Base admin dashboard and verify that the request headers contain X-WP-Nonce.

Common Pitfalls

  • Nonce Expiration: WordPress nonces expire after a set time (default is 12 hours). If your user leaves the admin dashboard open for a long time, the nonce might become invalid. You may need to implement a "nonce refresh" pattern if your UI is a long-lived Single Page Application.
  • Incorrect Action Name: Using wp_create_nonce('my-custom-action') in PHP but trying to verify with 'wp_rest' in the callback will always fail. Keep your action names consistent.
  • Caching: Never cache pages that contain localized nonces. If the nonce is cached, the "number used once" becomes a "number used many times," which is a security vulnerability.

Recap

Securing your endpoints with nonces is non-negotiable for intermediate plugin development. We've moved from basic permission checks to robust CSRF protection by generating tokens in PHP, injecting them into the JS environment, and ensuring they are sent via the X-WP-Nonce header. This workflow keeps your Knowledge Base plugin secure against common web vulnerabilities.

Up next: Advanced Sanitization Techniques where we will look at strictly enforcing data schemas for incoming JSON objects.

Similar Posts