Verifica nonce nelle API REST?

22 dic 2018, 03:25:13
Visualizzazioni: 21.6K
Voti: 9

Vorrei comprendere le best practice riguardo alla validazione dei nonce nelle API REST.

Vedo molte persone parlare del nonce wp_rest per le richieste REST. Ma esaminando il codice core di WordPress, ho notato che wp_rest è solo un nonce per validare lo stato di un utente loggato, se non è presente, la richiesta viene semplicemente eseguita come ospite.

Detto questo, dovrei inviare due nonce quando effettuo una richiesta POST a una API REST? Uno per l'autenticazione wp_rest e un altro per l'azione foo_action?

Se sì, come dovrei inviare i nonce wp_rest e foo_action in JavaScript e, in PHP, qual è il punto corretto per validare questi nonce? (Intendo validate_callback per un argomento? permission_callback?)

0
Tutte le risposte alla domanda 3
4
12

Dovresti passare il nonce speciale wp_rest come parte della richiesta. Senza di esso, l'oggetto global $current_user non sarà disponibile nella tua classe REST. Puoi passarlo in diversi modi, da $_GET a $_POST agli header.

Il nonce dell'azione è opzionale. Se lo aggiungi, non potrai utilizzare l'endpoint REST da un server esterno, ma solo da richieste inviate all'interno di WordPress stesso. L'utente può autenticarsi utilizzando Basic Auth, OAuth2 o JWT da un server esterno anche senza il nonce wp_rest, ma se aggiungi anche un nonce dell'azione, non funzionerà.

Quindi il nonce dell'azione è opzionale. Aggiungilo se vuoi che l'endpoint funzioni solo localmente.

Esempio:

/**
* Primo passo, registrare, localizzare e accodare il 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' );

/**
* Secondo passo, la richiesta sul file 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("Successo!");
    }).fail(function (xhr) {
        console.log(results);
        alert("Errore!");
    });
});

/**
* Terzo passo, l'endpoint REST stesso
*/
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( 'Spiacente, Some Value deve essere lungo almeno 5 caratteri.', 400 );
        }

        // Poiché stiamo passando l'header "X-WP-Nonce", questo funzionerà:
        $user = wp_get_current_user();

        if ( $user instanceof WP_User ) {
            return new WP_REST_Response( 'Spiacente, non è stato possibile ottenere il nome.', 400 );
        } else {
            return new WP_REST_Response( 'Il tuo nome utente è: ' . $user->display_name, 200 );
        }
    }
}
22 dic 2018 03:25:13
Commenti

È importante che aggiungi anche 'required' => true agli argomenti di foo_nonce, altrimenti è ancora possibile inviare richieste all'endpoint senza il nonce.

Dylan Dylan
5 giu 2019 06:04:20

Da dove viene data.rest.nonce? Non vedo che sia definito nel tuo codice.

drwatsoncode drwatsoncode
10 gen 2020 06:53:09

@Dylan È una buona idea renderlo obbligatorio, ma se non invii il nonce dell'azione fallirebbe sul wp_verify_nonce che viene eseguito prima di eseguire l'azione

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

@drwatsoncode data.rest.nonce proviene da wp_localize_script nel codice di esempio

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

Basandosi su quanto scritto da @lucas-bustamante (che mi è stato di grande aiuto!), una volta configurato l'header X-WP-Nonce nelle tue route personalizzate puoi fare quanto segue:

    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 che il permission_callback si trova a livello root e non sotto args (documentato qui) e ho rimosso il controllo aggiuntivo del nonce da args poiché il controllo delle sole autorizzazioni fallirà se il nonce non è valido o non è fornito (ho testato approfonditamente e posso confermare che ricevo un errore quando il nonce non è fornito o non è valido).

26 feb 2020 14:50:22
0

Una cosa da notare nella risposta di @Lucas Bustamante è che il processo di verifica descritto è un'autenticazione basata sull'utente. Ciò significa che se hai un endpoint API anonimo che non richiede un utente, semplicemente non fornendo l'header X-WP-NONCE supererai il controllo del nonce descritto. Fornire un nonce errato genererà comunque un errore.

Il motivo è che rest_cookie_check_errors, che è la funzione che esegue la verifica, imposterà semplicemente current_user a vuoto se non viene fornito alcun nonce. Questo funziona bene quando è richiesto un utente, ma non altrimenti. (vedi: https://developer.wordpress.org/reference/functions/rest_cookie_check_errors/)

Se vuoi espandere la risposta di Lucas per includere anche endpoint anonimi, puoi aggiungere un controllo manuale del nonce all'inizio del tuo endpoint, in questo modo:

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