Remember This When Creating Dynamic Nonces for WordPress REST API

Dear Donny,
One little tip for your future self if you’re creating a nonce dynamically for REST API requests with WordPress and its REST API: always use the correct action string — ‘wp_rest‘ — when generating the nonce. Which means, you would generate your nonce using:

wp_create_nonce('wp_rest')

Security is the most single important detail, this detail is where the API would be properly authenticated and authorized if the user is logged in.

We are going to step you through exactly why that matters, how to make that right, what to avoid doing wrong, and how to securely work with the WordPress REST API here. This guide will help you ensure that your authentication remains solid, whether you are building custom endpoints, integrating third-party apps, or just playing with the REST API.

What is a Nonce in WordPress?

Now, before we get into details of REST API, let us quickly revise what a nonce is in WordPress.

A nonce is the short name of number used only once, which is a security token for protecting URLs and Forms from misuse like CSRF (Cross-Site Request Forgery) attacks. WordPress makes heavy use of nonces to ensure that requests are originating from a legitimate site, usually a logged-in user using the sites UI.

WordPress nonces are time-sensitive tokens associated with a user session and a particular action rather than nonces in the cryptographic sense. That means they timeout after a fixed period (typically between 12 or 24 hours) and must be dynamically recreated once they have expired.

What Is a Nonce That REST API Requests Require a Unique Nonce

No matter if you are calling WordPress’s REST API through JavaScript code or if the request comes from another client, a nonce has to be passed in the request headers for authenticated actions such as creating a post, updating a setting or removing something.

So the nonce needs to be created for the exact action: ‘wp_rest‘ that the REST API requires. If you pass a nonce generated for a different action (or none at all), your request will be rejected with a 403 Forbidden response or similar authorization error.

Why?

WordPress uses the action string you pass to wp_create_nonce() to verify that the nonce is being used for its intended purpose. If the action string does not match the value expected by the REST API, the validation will fail.

In brief: if the REST API calls are authenticated by a nonce, use wp_create_nonce('wp_rest').

Generate Nonce Via AJAX

You always generate nonce using AJAX, why? Because you need the page to be static, a cached page using w3 total cache, yet, user can still request backends as an authenticated user, or non authenticated user but still more secure.

This is how you do it in JS:

const ajaxRequest   = 'https://deardonny.com/wp-admin/admin-ajax.php';
let nonce;
const isEmpty = (value) => {
    return (value == null || (typeof value === "string" && value.trim().length === 0));
}

const generateNonce = async (token) => {
    const data = {
        action: 'generate_nonce',
        // cf_token: token
    }
    const response = await fetch(`${ajaxRequest}`,
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams(data)
        }
    );
    if (!response.ok) {
        throw new Error('Network response was not ok');
    }
    const generatedNonce = await response.text();
    return generatedNonce;
}

window.addEventListener('DOMContentLoaded', async ()=>{
    if(isEmpty(nonce)){
        nonce = await generateNonce();
        console.log('Nonce Generated via load');
        document.dispatchEvent(new Event("nonceReady"));
    }        
});

Below is the PHP part

// Generate Nonce using Fetch - JS script is at js/main.js
function generate_nonce() {
    $nonce = wp_create_nonce('wp_rest');
    echo $nonce;
    wp_die();
}
add_action('wp_ajax_generate_nonce', 'generate_nonce');
add_action('wp_ajax_nopriv_generate_nonce', 'generate_nonce');

Donny, take a look at this line in JS:

document.dispatchEvent(new Event("nonceReady"));

This part is important. Any kind of action requiring backends, will be fired if only nonceReady event has been dispatched.

So, in other event, the code is like this:

document.addEventListener('nonceReady', function(){});

Thank me later!