Skip to main content

Send authenticated requests from an add-on’s admin screen without a stale nonce

Free Intermediate

Benecaster’s admin renews the page’s wp_rest nonce in place every 12 hours and whenever a request is refused with 403 rest_cookie_invalid_nonce. An add-on bundle does not get Benecaster’s own request helper, so its requests must read window.benecasterAdmin.nonce at the moment they are sent.

  • Never copy the nonce into a constant when your bundle loads. The copy is still the page-load nonce a day later, and every request your screen makes then fails while Benecaster’s keep working.
  • To recover from a refusal yourself, ask WordPress’s admin-ajax.php?action=rest-nonce, whose URL is window.benecasterAdmin.ajaxUrl, not GET /benecaster/v1/nonce: WordPress refuses every REST request carrying an expired nonce, that route included. Write the answer back to window.benecasterAdmin.nonce so Benecaster’s requests use it too.
  • A logged-out browser is not refused for its nonce. It sends no login cookie, so it is served as a visitor and gets 401, or the same 403 rest_forbidden an account without the capability gets. To tell those apart, ask the rest-nonce action: it answers 0 when the browser is logged out. Retrying cannot help then, so tell the user to log in again.

See Admin Localized Data for the rest of window.benecasterAdmin.

Code

async function myAddonFetch( path, init = {} ) {
    const send = () => fetch( `/wp-json/my-addon/v1/${ path }`, {
        ...init,
        credentials: 'same-origin',
        // Read at send time: Benecaster may have renewed it since the last call.
        headers: { 'Content-Type': 'application/json', ...init.headers, 'X-WP-Nonce': window.benecasterAdmin.nonce },
    } );

    let response = await send();
    if ( 403 === response.status ) {
        const { code } = await response.clone().json().catch( () => ( {} ) );
        if ( 'rest_cookie_invalid_nonce' === code && window.benecasterAdmin.ajaxUrl ) {
            const fresh = ( await ( await fetch( `${ window.benecasterAdmin.ajaxUrl }?action=rest-nonce`, { credentials: 'same-origin' } ) ).text() ).trim();
            if ( /^[a-f0-9]{10}$/.test( fresh ) ) {
                window.benecasterAdmin.nonce = fresh;
                response = await send();
            }
        }
    }
    return response;
}

View on GitHub →

Need this built rather than just documented? See our services →