7

Following a link comment here which led me to these docs, I've tried to set up a nonce for user authentication.

I added:

wp_localize_script( 'wp-api', 'wpApiSettings', array(
    'root' => esc_url_raw( rest_url() ),
    'nonce' => wp_create_nonce( 'wp_rest' )
) );

to the if statement checking the user is logged in and able to do the things that will be asked in the REST call.

I also added:

        beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
        },

to my api call jQuery class.

I got an error telling me wpApiSettings does not exist. What have I done wrong?

1 Answer 1

26

First off..

(This info is for other readers who don't know yet about this.) There are two ways of how you can authenticate your REST API request:

You can read more on the official REST API Handbook here, and this answer is for the standard cookie authentication, where the nonce should be sent either via a GET/POST query parameter named _wpnonce or a (custom HTTP) header named X-WP-Nonce.

Options for sending the cookie nonce

  • Easy option: Append _wpnonce to the URL of the REST API endpoint. This works with GET, POST, etc. requests including JSON payloads.

    jQuery.ajax({
        method: 'POST',
    
        // _wpnonce as a GET/$_GET query parameter
        url: '/path/to/endpoint?_wpnonce=<nonce>',
    
        data: { foo: 'bar', baz: 1 },
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    
  • Or add _wpnonce to the request body.

    jQuery.ajax({
        method: 'POST',
        url: '/path/to/endpoint',
    
        // _wpnonce as a POST/$_POST query parameter
        // but can be GET; see the `method` above which defaults to GET when not specified
        data: { foo: 'bar', baz: 1, _wpnonce: '<nonce>' },
    
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    
  • Or particularly when sending JSON payload (just like the example code in this question): Add X-WP-Nonce to the request headers. This option also works well with GET, POST, etc. requests.

    jQuery.ajax({
        method: 'POST',
        url: '/path/to/endpoint',
        data: JSON.stringify( { foo: 'bar', baz: 1 } ), // sending a JSON-encoded string
        contentType: 'application/json; charset=utf-8', // and a JSON Content-Type header
    
        // Send the nonce as part of the headers.
        beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', '<nonce>' );
        },
    
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    

Now an example:

PHP part: (in your theme functions file or your plugin file)

// Register a dummy REST API endpoint..
add_action( 'rest_api_init', 'my_register_rest_routes' );
function my_register_rest_routes() {
    register_rest_route( 'my-plugin/v1', '/foo', [
        'methods'  => 'POST',
        'callback' => function ( $request ) {
            return [
                $request->get_params(),
                'Is user logged-in: ' . ( is_user_logged_in() ? 'Yes' : 'NO' ),
                'Can user publish_posts: ' . ( current_user_can( 'publish_posts' ) ? 'Yes' : 'NO' )
            ];
        },
    ] );
}

add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );
function my_enqueue_scripts() {
    // Enqueue the script which makes the AJAX call to /wp-json/my-plugin/v1/foo.
    wp_enqueue_script( 'my-script', '/path/to/my-script.js', [ 'jquery' ] );

    // Register custom variables for the AJAX script.
    wp_localize_script( 'my-script', 'myScriptVars', [
        'root'  => esc_url_raw( rest_url() ),
        'nonce' => wp_create_nonce( 'wp_rest' ),
    ] );
}

Notes:

  1. Remember that the first parameter for wp_enqueue_script()my-script in the above example — is exactly the same as the first parameter for wp_localize_script(). And that parameter is the script handle which is a unique slug as an identifier for the script you are enqueueing or localizing.

  2. If those parameters don't match, then the script wouldn't be localized, and the JS object — myScriptVars in the above example — would be an undefined which could result in the error such as the one mentioned in the question ("wpApiSettings does not exist"). :)

JS part: (in my-script.js or whatever is the file name...)

Here, we add _wpnonce to the request body.

jQuery.ajax({
    method: 'POST',
    url: myScriptVars.root + 'my-plugin/v1/foo',
    data: { foo: 'bar', baz: 1, _wpnonce: myScriptVars.nonce },
    dataType: 'json',
    success: function ( data ) {
        console.log( data );
    },
});

Notes:

The above and the other JS code in this answer uses jQuery's ajax().

3
  • 1
    Ah, of course! I feel so foolish, of course the localisation must be done before headers. Doh. Great answer. Sep 13, 2019 at 13:39
  • 2
    Note for the slowest among us (like me) the first param of wp_localize_script is the handle you registered your script with to enqueue it. Sep 13, 2019 at 14:13
  • 1
    Thanks, I've added that to the answer.. :)
    – Sally CJ
    Sep 13, 2019 at 15:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.