¿Cómo verificar nonce en la API REST de WordPress?

22 dic 2018, 03:25:13
Vistas: 21.6K
Votos: 9

Me gustaría entender las mejores prácticas con respecto a la validación de nonces en las APIs REST.

Veo a muchas personas hablando sobre el nonce wp_rest para las peticiones REST. Pero al examinar el código del núcleo de WordPress, observé que wp_rest es simplemente un nonce para validar el estado de un usuario conectado, si no está presente, simplemente ejecuta la petición como invitado.

Dicho esto, ¿debería enviar dos nonces al realizar una petición POST a una API REST? Uno para autenticación wp_rest y otro para la acción foo_action?

Si es así, ¿cómo debería enviar los nonces wp_rest y foo_action en JavaScript, y en PHP, ¿cuál es el lugar correcto para validar esos nonces? (Me refiero a validate_callback para un argumento? ¿permission_callback?)

0
Todas las respuestas a la pregunta 3
4
12

Debes pasar el nonce especial wp_rest como parte de la solicitud. Sin él, el objeto global $current_user no estará disponible en tu clase REST. Puedes pasarlo de varias maneras, desde $_GET hasta $_POST o encabezados.

El nonce de acción es opcional. Si lo agregas, no podrás usar el endpoint REST desde un servidor externo, solo desde solicitudes despachadas desde dentro del propio WordPress. El usuario puede autenticarse usando Basic Auth, OAuth2, o JWT desde un servidor externo incluso sin el nonce wp_rest, pero si también agregas un nonce de acción, no funcionará.

Así que el nonce de acción es opcional. Agrégalo si quieres que el endpoint funcione solo localmente.

Ejemplo:

/**
* Primer paso, registrar, localizar y encolar el 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' );

/**
* Segundo paso, la solicitud en el archivo JavaScript
*/
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("¡Éxito!");
    }).fail(function (xhr) {
        console.log(results);
        alert("¡Error!");
    });
});

/**
* Tercer paso, el endpoint REST en sí
*/
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( 'Lo siento, Some Value debe tener al menos 5 caracteres de longitud.', 400 );
        }

        // Como estamos pasando el encabezado "X-WP-Nonce", esto funcionará:
        $user = wp_get_current_user();

        if ( $user instanceof WP_User ) {
            return new WP_REST_Response( 'Lo siento, no se pudo obtener el nombre.', 400 );
        } else {
            return new WP_REST_Response( 'Tu nombre de usuario es: ' . $user->display_name, 200 );
        }
    }
}
22 dic 2018 03:25:13
Comentarios

Es importante que también agregues 'required' => true a los argumentos de foo_nonce, de lo contrario aún podrías publicar en el endpoint sin el nonce.

Dylan Dylan
5 jun 2019 06:04:20

¿De dónde viene data.rest.nonce? No veo que esté definido en tu código.

drwatsoncode drwatsoncode
10 ene 2020 06:53:09

@Dylan Es una buena idea hacerlo requerido, pero si no envías el nonce de acción, fallaría en el wp_verify_nonce que se ejecuta antes de ejecutar la acción

Lucas Bustamante Lucas Bustamante
17 oct 2021 23:25:29

@drwatsoncode data.rest.nonce proviene de wp_localize_script en el código de ejemplo

Lucas Bustamante Lucas Bustamante
17 oct 2021 23:39:03
0

Continuando con lo que escribió @lucas-bustamante (¡lo cual me ayudó muchísimo!), una vez que tienes configurado el encabezado X-WP-Nonce en tus rutas personalizadas, puedes hacer lo siguiente:

    register_rest_route('v1', '/my_post', [
        'methods' => WP_REST_Server::CREATABLE,
        'callback' => [$this, 'create_post'],
        'args' => [
            'post_title' => [
                'required' => true,
            ],
            'post_excerpt' => [
                'required' => true,
            ]
        ],
        'permission_callback' => function ( ) {
            return current_user_can( 'publish_posts' );
        },
    ]);

Nota que el permission_callback está en el nivel raíz y no bajo args (documentado aquí) y he eliminado la verificación adicional de nonce de args ya que al verificar solo el permiso fallará si el nonce no es válido o no se proporciona (lo he probado extensamente y puedo confirmar que obtengo un error cuando no se suministra el nonce o es inválido).

26 feb 2020 14:50:22
0

Un aspecto a destacar sobre la respuesta de @Lucas Bustamante es que el proceso de verificación descrito es una autenticación basada en usuario. Esto significa que si tienes un punto final de API anónimo que no requiere un usuario, simplemente al no proporcionar el encabezado X-WP-NONCE pasarás la verificación de nonce descrita. Sin embargo, proporcionar un nonce incorrecto seguirá generando un error.

La razón de esto es que rest_cookie_check_errors, que es lo que realiza la verificación, simplemente establecerá el current_user como vacío si no se proporciona un nonce. Esto funciona bien cuando se requiere un usuario, pero no en otros casos. (ver: https://developer.wordpress.org/reference/functions/rest_cookie_check_errors/)

Si deseas ampliar la respuesta de Lucas para incluir también puntos finales anónimos, puedes agregar una verificación manual de nonce al inicio de tu punto final, así:

if ( !$_SERVER['HTTP_X_WP_NONCE'] || !wp_verify_nonce( $_SERVER['HTTP_X_WP_NONCE'], 'wp_rest' ) ) {
    header('HTTP/1.0 403 Forbidden');
    exit;
}
15 feb 2023 14:31:29