I would like to understand the best practices regarding nonce validation in REST APIs.

I see a lot of people talking about `wp_rest` nonce for REST requests. But upon looking on WordPress core code, I saw that `wp_rest` is just a nonce to validate a logged in user status, if it's not present, it just runs the request as guest.

That said, should I submit two nonces upon sending a POST request to a REST API? One for authentication `wp_rest` and another for the action `foo_action`?

If so, how should I send `wp_rest` and `foo_action` nonce in JavaScript, and, in PHP, what's the correct place to validate those nonces? (I mean validate\_callback for a arg? permission\_callback?)
['https://wordpress.stackexchange.com/questions/323637', 'https://wordpress.stackexchange.com', 'https://wordpress.stackexchange.com/users/27278/']
You should pass the special `wp_rest` nonce as part of the request. Without it, the `global $current_user` object will not be available in your REST class. You can pass this from several ways, from $\_GET to $\_POST to headers.

The action nonce is optional. If you add it, you can't use the REST endpoint from an external server, only from requests dispatched from within WordPress itself. The user can authenticate itself using [Basic Auth](https://github.com/WP-API/Basic-Auth), [OAuth2](https://github.com/WP-API/OAuth2), or [JWT](https://github.com/WP-API/jwt-auth) from an external server even without the `wp_rest` nonce, but if you add an action nonce as well, it won't work.

So the action nonce is optional. Add it if you want the endpoint to work locally only.

Example:

```
/**
* First step, registering, localizing and enqueueing the JavaScript
*/
wp_register_script( 'main-js', get_template_directory_uri() . '/js/main.js', [ 'jquery' ] );
wp_localize_script( 'main-js', 'data', [
    'rest' => [
        'endpoints' => [
            'my_endpoint'       => esc_url_raw( rest_url( 'my_plugin/v1/my_endpoint' ) ),
        ],
        'timeout'   => (int) apply_filters( "my_plugin_rest_timeout", 60 ),
        'nonce'     => wp_create_nonce( 'wp_rest' ),
        //'action_nonce'     => wp_create_nonce( 'action_nonce' ), 
    ],
] );
wp_enqueue_script( 'main-js' );

/**
* Second step, the request on the JavaScript file
*/
jQuery(document).on('click', '#some_element', function () {
    let ajax_data = {
        'some_value': jQuery( ".some_value" ).val(),
        //'action_nonce': data.rest.action_nonce
    };

    jQuery.ajax({
        url: data.rest.endpoints.my_endpoint,
        method: "GET",
        dataType: "json",
        timeout: data.rest.timeout,
        data: ajax_data,
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', data.rest.nonce);
        }
    }).done(function (results) {
        console.log(results);
        alert("Success!");
    }).fail(function (xhr) {
        console.log(results);
        alert("Error!");
    });
});

/**
* Third step, the REST endpoint itself
*/
class My_Endpoint {
        public function registerRoutes() {
        register_rest_route( 'my_plugin', 'v1/my_endpoint', [
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => [ $this, 'get_something' ],
            'args'                => [
                'some_value'     => [
                    'required' => true,
                ],
            ],
            'permission_callback' => function ( WP_REST_Request $request ) {
                return true;
            },
        ] );
    }

    /**
    *   @return WP_REST_Response
    */
    private function get_something( WP_REST_Request $request ) {
        //if ( ! wp_verify_nonce( $request['nonce'], 'action_nonce' ) ) {
        //  return false;
        //}

        $some_value        = $request['some_value'];

        if ( strlen( $some_value ) < 5 ) {
            return new WP_REST_Response( 'Sorry, Some Value must be at least 5 characters long.', 400 );
        }

        // Since we are passing the "X-WP-Nonce" header, this will work:
        $user = wp_get_current_user();

        if ( $user instanceof WP_User ) {
            return new WP_REST_Response( 'Sorry, could not get the name.', 400 );
        } else {
            return new WP_REST_Response( 'Your username name is: ' . $user->display_name, 200 );
        }
    }
}

```
One thing to note about @Lucas Bustamante's answer is that the verification process described is user-based authentication. This means if you have an anonymous API end point which doesn't require a user, then by simply by not providing the `X-WP-NONCE` header you'll pass the described nonce check. Supplying an incorrect nonce will still throw an error.

The reason for this is that the `rest_cookie_check_errors` which is what does the verification will simply set the `current_user` to empty if no nonce is provided. This works fine when a user is required, but not otherwise. (see: <https://developer.wordpress.org/reference/functions/rest_cookie_check_errors/>)

If you want to expand on Lucas' answer to also include annonymous end points, then you can add a manual nonce check to the start of your end point, like this:

```
if ( !$_SERVER['HTTP_X_WP_NONCE'] || !wp_verify_nonce( $_SERVER['HTTP_X_WP_NONCE'], 'wp_rest' ) ) {
    header('HTTP/1.0 403 Forbidden');
    exit;
}

```