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.
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):
JAVASCRIPTimport 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():
PHPregister_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:
PHPif ( ! 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
- Locate your existing
wp_localize_scriptcall in the plugin's PHP file. - Add the
noncekey to the localized object usingwp_create_nonce('wp_rest'). - Update your JavaScript service layer to ensure
apiFetchis configured with theX-WP-Nonceheader. - 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.
Work with me

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds โ familiar editing, modern speed.

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard โ by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.