¿Cómo configurar correctamente un nonce AJAX para la API REST de WordPress?

13 sept 2019, 13:37:57
Vistas: 16.4K
Votos: 8

Siguiendo un comentario con enlace aquí que me llevó a esta documentación, intenté configurar un nonce para autenticación de usuarios.

Agregué:

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

al condicional que verifica si el usuario está logueado y puede realizar las acciones solicitadas en la llamada REST.

También agregué:

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

a mi clase jQuery que realiza la llamada API.

Recibí un error indicando que wpApiSettings no existe. ¿Qué hice mal?

0
Todas las respuestas a la pregunta 1
3
28

Primero que nada...

(Esta información es para otros lectores que aún no lo saben.) Hay dos formas de autenticar tu solicitud REST API:

Puedes leer más en el manual oficial de REST API aquí, y esta respuesta es para la autenticación estándar por cookies, donde el nonce debe ser enviado ya sea a través de un parámetro de consulta GET/POST llamado _wpnonce o un encabezado (HTTP personalizado) llamado X-WP-Nonce.

Opciones para enviar el nonce de la cookie

  • Opción fácil: Agregar _wpnonce a la URL del endpoint REST API. Esto funciona con solicitudes GET, POST, etc. incluyendo cargas JSON.

    jQuery.ajax({
        method: 'POST',
    
        // _wpnonce como un parámetro de consulta GET/$_GET
        url: '/path/to/endpoint?_wpnonce=<nonce>',
    
        data: { foo: 'bar', baz: 1 },
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    
  • O añadir _wpnonce al cuerpo de la solicitud.

    jQuery.ajax({
        method: 'POST',
        url: '/path/to/endpoint',
    
        // _wpnonce como un parámetro POST/$_POST
        // pero puede ser GET; mira el `method` arriba que por defecto es GET cuando no se especifica
        data: { foo: 'bar', baz: 1, _wpnonce: '<nonce>' },
    
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    
  • O particularmente al enviar carga JSON (como el código de ejemplo en esta pregunta): Añadir X-WP-Nonce a los encabezados de la solicitud. Esta opción también funciona bien con solicitudes GET, POST, etc.

    jQuery.ajax({
        method: 'POST',
        url: '/path/to/endpoint',
        data: JSON.stringify( { foo: 'bar', baz: 1 } ), // enviando una cadena codificada en JSON
        contentType: 'application/json; charset=utf-8', // y un encabezado Content-Type JSON
    
        // Enviar el nonce como parte de los encabezados.
        beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', '<nonce>' );
        },
    
        dataType: 'json',
        success: function ( data ) {
            console.log( data );
        },
    });
    

Ahora un ejemplo:

Parte PHP: (en el archivo de funciones de tu tema o tu plugin)

// Registrar un endpoint REST API de prueba...
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(),
                '¿Usuario autenticado?: ' . ( is_user_logged_in() ? 'Sí' : 'NO' ),
                '¿Usuario puede publicar posts?: ' . ( current_user_can( 'publish_posts' ) ? 'Sí' : 'NO' )
            ];
        },
    ] );
}

add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );
function my_enqueue_scripts() {
    // Encolar el script que hace la llamada AJAX a /wp-json/my-plugin/v1/foo.
    wp_enqueue_script( 'my-script', '/path/to/my-script.js', [ 'jquery' ] );

    // Registrar variables personalizadas para el script AJAX.
    wp_localize_script( 'my-script', 'myScriptVars', [
        'root'  => esc_url_raw( rest_url() ),
        'nonce' => wp_create_nonce( 'wp_rest' ),
    ] );
}

Notas:

  1. Recuerda que el primer parámetro para wp_enqueue_script()my-script en el ejemplo anterior — es exactamente el mismo que el primer parámetro para wp_localize_script(). Y ese parámetro es el handle del script que es un slug único como identificador para el script que estás encolando o localizando.

  2. Si esos parámetros no coinciden, entonces el script no se localizará, y el objeto JS — myScriptVars en el ejemplo anterior — sería un undefined lo que podría resultar en un error como el mencionado en la pregunta ("wpApiSettings no existe"). :)

Parte JS: (en my-script.js o cualquiera que sea el nombre del archivo...)

Aquí, añadimos _wpnonce al cuerpo de la solicitud.

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 );
    },
});

Notas:

El código JS anterior y los otros en esta respuesta usan ajax() de jQuery.

13 sept 2019 14:59:33
Comentarios

¡Ah, por supuesto! Me siento tan tonto, claro que la localización debe hacerse antes de los encabezados. Doh. Gran respuesta.

Matthew Brown aka Lord Matt Matthew Brown aka Lord Matt
13 sept 2019 16:39:11

Nota para los más lentos entre nosotros (como yo): el primer parámetro de wp_localize_script es el handle con el que registraste tu script para encolarlo.

Matthew Brown aka Lord Matt Matthew Brown aka Lord Matt
13 sept 2019 17:13:53

Gracias, he añadido eso a la respuesta.. :)

Sally CJ Sally CJ
13 sept 2019 18:12:21