Idéntico wp_rest nonce devuelto desde rest_api

Nov 02 2020

TLDR: ¿Cómo puedo forzar la devolución de un nuevo nonce, para que AJAX funcione sin actualizar la página?

Después de la solicitud de Nonces y Ajax a la API REST y la verificación , implementé la funcionalidad de Nonce, con permisos de devolución de llamada y la 'intención' de mis rutas ahora es segura.

Pongo Axios en cola, con wp_rest nonce como configuración de script local.

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

Configuré mis encabezados Ajax con axiosScriptVars.nonce. POST - esto funciona. Devuelvo un NONCE de la respuesta de mi punto final REST_API y actualizo mi encabezado Ajax listo para el próximo POST ...

Ejemplo de mi ruta de inicio y cierre de sesión:

function ajax_logout() {
    try {
    wp_logout();
    $nonce = wp_create_nonce( 'wp_rest' ); return array('loggedin'=>false, 'message'=>__('Logged out successfully'), 'name'=>false, 'email'=>false, 'nonce'=>$nonce );
    } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n";
        die;
    }
}


function ajax_login(){
    $_POST = json_decode(file_get_contents("php://input"),true); $info = array();
    $info['user_login'] = !empty($_POST['username']) ? sanitize_text_field( $_POST['username'] ) : null; $info['user_password'] = !empty( $_POST['password'] ) ? sanitize_text_field( $_POST['password'] ) : null;
    $info['remember'] = true; $user = wp_signon( $info, true ); $nonce = wp_create_nonce( 'wp_rest' );

    if ( is_wp_error($user) ){ return array('loggedin'=>false, 'message'=>__('Wrong username or password.'), 'name'=>false, 'email'=>false, 'nonce'=>$nonce );
    } else {
        return array('loggedin'=>true, 'message'=>__('Login successful'), 'name'=>$user->data->display_name, 'email'=>$user->data->user_email, 'nonce'=>$nonce );
    }
}


add_action( 'rest_api_init', function () {
    register_rest_route( 'rw-user/v1', '/log-in', array(
        'methods' => 'POST',
        'callback' => 'ajax_login',
        'permission_callback' => '__return_true'
    ));
    register_rest_route('rw-user/v1','/log-out', array(
        'methods' => 'POST',
        "callback" => 'ajax_logout',
        "permission_callback" => function () {
            return current_user_can( 'read' );
        }
    ));
});

PUBLICACIÓN falla porque el Nuevo NONCE es idéntico al anterior.

Así que pensé que Wordpress debía devolver un nuevo nonce en el encabezado de respuesta ... Reviso y veo el encabezado "x-wp-nonce", ¡solo que también es idéntico!

(Leí algo sobre no usar wp_json_success en rutas REST, ya que el resto de la API ya convierte el retorno de su función en una respuesta json y establece los encabezados correctos, etc.)

Cuando actualizo la página, obtengo un nuevo nonce y mi AJAX ahora funciona ...

¿Cómo puedo forzar la devolución de un nuevo nonce para que AJAX funcione sin actualizar la página?

Esta publicación es algo similar: servir nonces a través de AJAX no es refrescante nonce, devuelve el error 403 , pero no puedo ver nada en mi código que esté cambiando el usuario e invalidando el nonce ...

A menos que la ruta de inicio de sesión, ahora iniciada sesión, cambie el nonce, ¿cuál no se devuelve?


Editar

He refactorizado usando la respuesta de Sally. Todo funciona (aunque tuve que borrar el caché de mi navegador porque estaban sucediendo algunas cosas raras).

    // https://wordpress.stackexchange.com/questions/377570/identical-wp-rest-nonce-returned-from-rest-api

/* Ajax REST cookie stuff */
// https://remonpel.nl/2018/06/wordpress-rest-api-nonce-sense/

add_action('set_logged_in_cookie', function($cookie_value){
    $_COOKIE[ LOGGED_IN_COOKIE ] = $cookie_value;
}, PHP_INT_MAX);

// on log-ou, clear the global $_COOKIE the same way wp_logout() does. // todo: see if more cookies need this. add_action('clear_auth_cookie', function(){ $_COOKIE[ LOGGED_IN_COOKIE ] = ' ';
});

/* end Ajax REST cookie stuff */


function ajax_logout() {
    wp_logout();
    wp_set_current_user(0);
    return [
        'loggedin'  =>  false,
        'message'   =>  __('Logged out successfully'),
        'name'      =>  false,
        'email'     =>  false,
    ];
}


function ajax_login($request){ $user = wp_signon( array(
        'user_login'    => $request->get_param( 'username' ), 'user_password' => $request->get_param( 'password' ),
        'remember'      => true,
    ));
      // In case of errors like wrong password, return the error object/data.
if ( is_wp_error( $user ) ) { $data = [
        'failed' => $user, 'nonce' => wp_create_nonce( 'wp_rest' ), ]; return new WP_REST_Response($data, 200); // important if you want to display data client side
}
    wp_set_current_user( $user->ID ); // super important return [ 'loggedin' => is_user_logged_in(), 'message' => __('Login successful'), 'name' => $user->data->display_name, 
        'email'    =>   $user->data->user_email,
        'nonce'    =>   wp_create_nonce( 'wp_rest' ),
    ];
}



function read_permissions_check() {
    // Restrict endpoint to only users who have the edit_posts capability.
    // This can be extended or whatever
    if ( !current_user_can( 'read' ) ) {
        return new WP_Error( 'rest_forbidden', esc_html__( 'You shall not pass!', 'my-text-domain' ), array( 'status' => 401 ) );
    }
    return true;
}


add_action( 'rest_api_init', function () {
    register_rest_route( 'rw-user/v1', '/log-in', array(
        'methods' => 'POST',
        'callback' => 'ajax_login',
        'permission_callback' => '__return_true',
    ));
    register_rest_route('rw-user/v1','/log-out', array(
        'methods' => 'POST',
        "callback" => 'ajax_logout',
        'permission_callback' => 'read_permissions_check',
    ));
});

Respuestas

1 SallyCJ Nov 03 2020 at 04:54

A pesar de que ya lo descubrió o encontró una solución , dado que esta es una continuación de la otra pregunta, pensé que sería beneficioso publicar esta respuesta, así que espero que lo ayude. =)

WordPress usa la setcookie()función nativa en PHP para configurar las cookies de autenticación y $_COOKIEpara leer los valores de las cookies. Pero la cuestión es que PHP no actualiza inmediatamente los valores en la $_COOKIEmatriz y, en cambio, solo se actualizará una vez que se vuelva a cargar la página.

Entonces, debido a que WordPress nonces se basa en el usuario actual y las cookies de autenticación, deberá hacer esto para obtener el nonce correcto en la misma página / solicitud , es decir, sin recargas de página:

  1. Actualice inmediatamente la $_COOKIEmatriz una vez que haya llamado wp_signon()(qué llamadas wp_set_auth_cookie()). Y puedes hacerlo usando el set_logged_in_cookiegancho .

  2. Y luego llame wp_set_current_user()después de iniciar sesión en el usuario; si no lo llama, el nonce generado no usará el usuario actual y las funciones como is_user_logged_in()y current_user_can()también regresarán false.

Ejemplo de trabajo

function wpse_377570( $logged_in_cookie ) {
    $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;
}
add_action( 'set_logged_in_cookie', 'wpse_377570' );

function ajax_login( $request ) { // Logs the user in and set the authentication cookies. $user = wp_signon( array(
        'user_login'    => $request->get_param( 'username' ), 'user_password' => $request->get_param( 'password' ),
        'remember'      => true,
    ) );

    // In case of errors like wrong password, return the error object/data.
    if ( is_wp_error( $user ) ) { return $user;
    }

    // Now set the current user so that we get the correct nonce based on
    // that user.
    wp_set_current_user( $user->ID );

    return [
        'nonce'    => wp_create_nonce( 'wp_rest' ),
        'loggedin' => is_user_logged_in(),
        // ... your other data.
    ];
}

Notas adicionales

  • La devolución de llamada del punto final de la API REST siempre recibe el objeto de solicitud (una WP_REST_Requestinstancia) como primer parámetro, que, entre otras funciones, tiene get_param()para recuperar un parámetro como una cadena de consulta de URL, un cuerpo de datos POST o una carga útil JSON, por lo que debe hacer uso de eso y las otras funciones , tal como lo hice en mi ejemplo. :)