8

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 Answers1

28

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().

Sally CJ
  • 40,362
  • 2
  • 29
  • 50