How to expose the REST nonce in the head with wp_localize_script in WordPress

Contents

Overview — What this tutorial does

This tutorial shows a complete, practical, secure way to expose the WordPress REST API nonce into the document head using wp_localize_script so your front-end JavaScript can authenticate cookie-based REST requests. It explains the purpose of the nonce, where/how WordPress validates it, the best practices (when to expose, when not to), caching and security considerations, and several concrete code examples for enqueueing, localizing, consuming the nonce in JavaScript, and validating it on a custom REST endpoint.

Why expose the REST nonce

  • Purpose: The REST nonce (created with wp_create_nonce(wp_rest)) is used with cookie-based authentication to protect REST routes that require a logged-in user. When the nonce is provided in the request header X-WP-Nonce, WordPress will treat that request as authenticated (subject to capability checks).
  • Common use: Themes/plugins often need to make authenticated requests from front-end JavaScript (for example, saving a user preference, posting content, or invoking custom REST endpoints). Exposing the nonce to JavaScript lets your client attach it to requests.
  • Why use wp_localize_script: wp_localize_script is a safe, standard way to print structured data (JSON-safe) tied to a specific enqueued script. It outputs a small inline JS object just before the enqueued script tag. By enqueuing the localized script in the head (in_footer = false), you can make the nonce available in the head.

Short summary of approach

  1. Register or enqueue a script handle that will be printed in the head (set in_footer = false).
  2. Call wp_localize_script with a unique JS object name and pass the REST nonce (plus useful values like rest root URL).
  3. Consume window.MySiteSettings.nonce (or whatever name you choose) in your front-end code and send it as the header X-WP-Nonce with fetch, jQuery.ajax, or wp.apiFetch.
  4. On the server, ensure your REST endpoints permission_callback verifies capability and/or checks the nonce (wp_verify_nonce) if you need explicit nonce validation.

Important security and caching notes

  • Only expose to users who need it: The REST nonce is meaningful for authenticated (cookie) requests. If you expose a nonce to anonymous users, it will be useless for authentication but can still lead to stale or cached values being visible. Prefer to only localize a nonce for logged-in users (wrap with is_user_logged_in()).
  • Cache carefully: Avoid exposing personalized nonces on pages that are aggressively cached and then served to other users. If a full page cache serves a logged-in users nonce to another user, that could be a problem. Best practice: do not cache pages that print per-user nonces, or use ESI/personalization techniques.
  • Nonce lifetime: WordPress nonces are time-limited (default lifecycle is two verification ticks, commonly interpreted as a 24-hour validity window implemented as two 12-hour ticks). Do not rely on a nonce as a long-term credential treat it as a short-time anti-CSRF token.
  • Header name: Use the exact header name X-WP-Nonce (case-insensitive on many servers, but keep this exact string for clarity).

Step-by-step: Enqueue, localize and ensure the script prints in the head

The key detail to ensure the localized object (that contains the nonce) actually appears in the head is to enqueue the script with in_footer = false. wp_localize_script injects the localized object immediately before the script element for the given handle. If the script is printed in the footer, the localized object will appear in the footer as well.

PHP: Minimal, correct example for front-end (functions.php or plugin)

This example registers a front-end script, localizes an object named MySiteSettings containing the REST nonce and rest_url(), and enqueues the script into the head by setting in_footer to false.

 wp_create_nonce( wp_rest ),
            root  => esc_url_raw( rest_url() ),
            site  => esc_url_raw( home_url( / ) ),
        )
        // Unique JS object name to avoid collisions
        wp_localize_script( handle, MySiteSettings, localize )
    }

    wp_enqueue_script( handle )
}
?>

Notes about the code:

  • wp_register_script then wp_enqueue_script: This is a good practice because wp_localize_script requires that the handle be registered (or enqueued) before localizing. wp_localize_script will print an inline script containing a JavaScript object named MySiteSettings just before the script tag for the handle.
  • esc_url_raw: sanitize URLs passed into JS so you avoid accidental client-side issues.
  • is_user_logged_in: limit exposure to logged-in users unless you intentionally need it for everyone.

How wp_localize_script prints the object

wp_localize_script generates inline JavaScript that looks like:

var MySiteSettings = {nonce:abc123..., root:https://example.com/wp-json/, site:https://example.com/}

That inline script is automatically printed immediately before the enqueued script tag for the handle. Because the script is registered/enqueued with in_footer = false, both the inline object and the script tag will appear in the document head (inside wp_head output).

JavaScript: Using the nonce

Below are common usage patterns to include the nonce in REST requests.

1) fetch API (modern browsers)

// frontend.js
const { root, nonce } = window.MySiteSettings

// Example: GET posts (if endpoint allows cookie auth)
fetch( root   wp/v2/posts, {
  method: GET,
  headers: {
    X-WP-Nonce: nonce,
    Content-Type: application/json
  },
  credentials: same-origin // ensure cookies are sent
})
.then( res => {
  if ( ! res.ok ) {
    throw new Error(Network response was not ok:    res.status )
  }
  return res.json()
})
.then( data => {
  console.log( posts, data )
})
.catch( err => console.error( err ) )

2) jQuery.ajax

// frontend.js
jQuery.ajax({
  url: window.MySiteSettings.root   wp/v2/posts,
  method: GET,
  dataType: json,
  headers: {
    X-WP-Nonce: window.MySiteSettings.nonce
  },
  xhrFields: {
    withCredentials: true
  }
}).done(function(data){
  console.log( data )
}).fail(function(xhr, status, error) {
  console.error( status, error )
})

3) wp.apiFetch

If you include the wp-api client, you can set default headers or pass headers per-call:

// With wp.apiFetch available:
wp.apiFetch({
  path: /wp/v2/posts,
  headers: {
    X-WP-Nonce: window.MySiteSettings.nonce
  }
}).then( data => console.log( data ) ).catch( e => console.error(e) )

Server-side: Validating the nonce for custom REST routes

For custom REST endpoints, use permission_callback to validate the request. You can check the nonce header from the WP_REST_Request object and use wp_verify_nonce. Also check capabilities (current_user_can) as appropriate.

 POST,
        callback            => myplugin_private_data_handler,
        permission_callback => myplugin_private_data_permission,
    ) )
} )

function myplugin_private_data_permission( WP_REST_Request request ) {
    // Get the nonce from the request headers (WP REST header names are normalized to lowercase)
    nonce = request->get_header( x-wp-nonce )

    // Verify nonce - context must match how it was generated: wp_rest
    if ( ! wp_verify_nonce( nonce, wp_rest ) ) {
        return new WP_Error( rest_forbidden, Invalid nonce, array( status => 403 ) )
    }

    // Optionally check a capability (example: user must be able to edit posts)
    if ( ! current_user_can( edit_posts ) ) {
        return new WP_Error( rest_forbidden, Insufficient permissions, array( status => 403 ) )
    }

    return true
}

function myplugin_private_data_handler( WP_REST_Request request ) {
    // At this point the nonce and capability were validated in permission callback
    return rest_ensure_response( array( success => true, data => secret ) )
}
?>

Note: WordPress core uses the same wp_rest action in core REST permission checks for most cookie-authenticated endpoints. Your custom endpoints must explicitly validate permissions via permission_callback. Do not rely exclusively on the presence of the nonce also check user capabilities as appropriate.

Extra examples and common variants

Conditionally localize only for logged in users

 wp_create_nonce( wp_rest ),
            root  => esc_url_raw( rest_url() ),
        ) )
    }
}
?>

Expose minimal data only (minimum surface area)

Only expose what you need: often all you need is the nonce and rest root. Avoid passing unnecessary PHP data into the JS object.

Testing with curl

To test an authenticated request using the nonce from a logged-in browser session, you typically need to include the users cookies and the X-WP-Nonce header. Here is an example curl invocation when testing from the server where cookies / auth are available (replace cookie and nonce values accordingly):

curl https://example.com/wp-json/myplugin/v1/private-data 
  -H X-WP-Nonce: abc123noncevalue 
  -H Content-Type: application/json 
  --cookie wordpress_logged_in_...=your_cookie_value 
  -d {some:payload}

Common pitfalls and troubleshooting

Problem Cause / Fix
Localized object appears in footer, not head Script was enqueued with in_footer = true. Register/enqueue with in_footer = false so it prints in the head.
Nonce value is missing for some users You may be conditionally localizing only for logged-in users (is_user_logged_in()). If users are anonymous, they won’t have a valid cookie auth nonce. Decide intentionally whether anonymous users should have any value.
Requests returned 401 or 403 despite nonce Ensure the request sends cookies (fetch must include credentials: same-origin or include) and header name is X-WP-Nonce. Also ensure the user is actually logged-in check wp_verify_nonce in permission callback if using custom endpoint.
Page is cached with a user-specific nonce Avoid printing per-user nonces on fully cached pages. Use dynamic blocks, edge-side includes, or do not cache that page.

Compact checklist before deploying

  • Register/enqueue the script with in_footer = false to have the localized object appear in the head.
  • Use a unique JS object name when calling wp_localize_script to avoid collisions.
  • Only expose the nonce to users who need it wrap in is_user_logged_in() for most authenticated flows.
  • Sanitize values before localizing: esc_url_raw() for URLs, cast nonces to string.
  • On the server, protect custom endpoints with permission_callback and verify the nonce / user capabilities.
  • Review caching: do not expose per-user nonces on publicly cached HTML.

Useful references

Final notes

Exposing the REST nonce with wp_localize_script is an established pattern: it is simple and effective when done with attention to scope, caching, and user state. Keep the nonce exposure minimal, validate carefully on the server side, and never treat the nonce as a long-term credential — it is an anti-CSRF token that helps WordPress identify and validate cookie-authenticated REST requests.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *