The WordPress REST API Requests opens doors for building dynamic, decoupled applications, but security remains paramount. Nonces (Number Used Once) are critical for verifying the authenticity of requests, preventing CSRF (Cross-Site Request Forgery) attacks, and ensuring only authorized users can perform actions. This guide explains how to securely integrate a JavaScript front end with WordPress nonce system and REST API, covering nonce generation, request handling, and error management.
Nonces protect against:
Example Scenario:
A front-end form allows users to delete posts. Without a nonce, an attacker could forge a request to delete arbitrary posts.

Use wp_create_nonce() to generate a nonce for specific actions (e.g., wp_rest for REST API requests).
// PHP Code
// functions.php or plugin file
function enqueue_scripts() {
wp_enqueue_script('my-app', get_template_directory_uri() . '/js/app.js', array(), '1.0', true);
// Generate a nonce for REST API requests
$nonce = wp_create_nonce('wp_rest');
// Pass data to JavaScript
wp_localize_script('my-app', 'wpApiSettings', array(
'root' => esc_url_raw(rest_url()),
'nonce' => $nonce,
'user' => get_current_user_id()
));
}
add_action('wp_enqueue_scripts', 'enqueue_scripts');
Key Functions:
For logged-out users, expose a REST endpoint to fetch nonces:
// PHP Code
add_action('rest_api_init', function () {
register_rest_route('myplugin/v1', '/nonce', array(
'methods' => 'GET',
'callback' => function () {
return new WP_REST_Response(array(
'nonce' => wp_create_nonce('wp_rest')
), 200);
},
'permission_callback' => '__return_true'
));
});
Attach the nonce to the X-WP-Nonce header in REST requests:
// Javascrpt
// app.js
fetch(wpApiSettings.root + 'myplugin/v1/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce
},
body: JSON.stringify({ title: 'New Post' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Set the nonce as a default header:
// app.js
axios.defaults.headers.common['X-WP-Nonce'] = wpApiSettings.nonce;
axios.post(wpApiSettings.root + 'myplugin/v1/posts', {
title: 'New Post'
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
Include the nonce in the beforeSend hook:
// AJAX Code
// app.js
$.ajax({
url: wpApiSettings.root + 'myplugin/v1/posts',
method: 'POST',
data: { title: 'New Post' },
beforeSend: function(xhr) {
xhr.setRequestHeader('X-WP-Nonce', wpApiSettings.nonce);
},
success: function(data) {
console.log(data);
},
error: function(error) {
console.error('Error:', error);
}
});
Verify the nonce in your REST API callback functions.
// PHP Code
add_action('rest_api_init', function () {
register_rest_route('myplugin/v1', '/posts', array(
'methods' => 'POST',
'callback' => function (WP_REST_Request $request) {
// Verify nonce
$nonce = $request->get_header('X_WP_Nonce');
if (!wp_verify_nonce($nonce, 'wp_rest')) {
return new WP_Error('invalid_nonce', 'Invalid nonce.', array('status' => 403));
}
// Proceed with authenticated logic
$post_id = wp_insert_post(array(
'post_title' => $request->get_param('title')
));
return new WP_REST_Response(array('id' => $post_id), 200);
},
'permission_callback' => function () {
return current_user_can('publish_posts');
}
));
});
Note: WordPress automatically checks wp_rest nonces for REST API routes. For custom actions, use wp_verify_nonce($nonce, ‘action_name’).
Nonces expire after 24 hours by default. Handle expiration gracefully by:
Check for a rest_cookie_invalid_nonce error in responses:
// javascript
// app.js
fetch(wpApiSettings.root + 'myplugin/v1/posts', {
headers: { 'X-WP-Nonce': wpApiSettings.nonce }
})
.then(response => {
if (response.status === 403) {
// Nonce expired – refresh it
return refreshNonce();
}
return response.json();
})
.then(data => console.log(data));
// Fetch a new nonce
function refreshNonce() {
return fetch(wpApiSettings.root + 'myplugin/v1/nonce')
.then(response => response.json())
.then(data => {
wpApiSettings.nonce = data.nonce;
localStorage.setItem('nonce', data.nonce); // Optional: Cache
});
}
Use an interceptor (Axios example):
// Javascript
// app.js
axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 403) {
return refreshNonce().then(() => {
return axios.request(error.config);
});
}
return Promise.reject(error);
}
);
// PHP Code
add_filter('nonce_life', function () {
return 12 * 3600; // 12 hours
});
Integrating WordPress nonces with a JavaScript front end ensures secure communication with the REST API, protecting against CSRF and unauthorized access. By generating nonces server-side, attaching them to requests, and handling expiration gracefully, you can build robust, secure applications that leverage WordPress backend capabilities. Always pair nonces with capability checks, HTTPS, and other security measures to create a defense-in-depth strategy.
With this guide, you’re equipped to implement secure, nonce-authenticated interactions in your next WordPress project.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.