Verificarea nonce în REST API

22 dec. 2018, 03:25:13
Vizualizări: 21.6K
Voturi: 9

Aș dori să înțeleg cele mai bune practici privind validarea nonce în REST API.

Văd mulți oameni vorbind despre nonce-ul wp_rest pentru cererile REST. Dar analizând codul WordPress core, am observat că wp_rest este doar un nonce pentru a valida starea unui utilizator autentificat, iar dacă nu este prezent, cererea este executată ca și vizitator.

Având în vedere acestea, ar trebui să trimit două nonce-uri când fac o cerere POST către un REST API? Unul pentru autentificare wp_rest și altul pentru acțiune foo_action?

Dacă da, cum ar trebui să trimit nonce-urile wp_rest și foo_action în JavaScript și, în PHP, care este locul corect pentru a valida aceste nonce-uri? (Mă refer la validate_callback pentru un argument? permission_callback?)

0
Toate răspunsurile la întrebare 3
4
12

Ar trebui să transmiți nonce-ul special wp_rest ca parte a cererii. Fără acesta, obiectul global $current_user nu va fi disponibil în clasa ta REST. Poți să-l transmiți în mai multe moduri, fie prin $_GET, $_POST sau în headere.

Nonce-ul de acțiune este opțional. Dacă îl adaugi, nu poți folosi endpoint-ul REST de pe un server extern, ci doar din cereri trimise din interiorul WordPress. Utilizatorul se poate autentifica folosind Basic Auth, OAuth2 sau JWT de pe un server extern chiar și fără nonce-ul wp_rest, dar dacă adaugi și un nonce de acțiune, nu va funcționa.

Deci nonce-ul de acțiune este opțional. Adaugă-l dacă vrei ca endpoint-ul să funcționeze doar local.

Exemplu:

/**
* Primul pas, înregistrarea, localizarea și încărcarea JavaScript-ului
*/
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' );

/**
* Al doilea pas, cererea din fișierul 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("Succes!");
    }).fail(function (xhr) {
        console.log(results);
        alert("Eroare!");
    });
});

/**
* Al treilea pas, endpoint-ul REST în sine
*/
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( 'Ne pare rău, Some Value trebuie să aibă minim 5 caractere.', 400 );
        }

        // Deoarece transmitem header-ul "X-WP-Nonce", acesta va funcționa:
        $user = wp_get_current_user();

        if ( $user instanceof WP_User ) {
            return new WP_REST_Response( 'Ne pare rău, nu am putut obține numele.', 400 );
        } else {
            return new WP_REST_Response( 'Numele tău de utilizator este: ' . $user->display_name, 200 );
        }
    }
}
22 dec. 2018 03:25:13
Comentarii

Este important să adaugi și 'required' => true la argumentele foo_nonce, altfel poți trimite în continuare la endpoint fără nonce.

Dylan Dylan
5 iun. 2019 06:04:20

De unde vine data.rest.nonce? Nu văd că este definit în codul tău.

drwatsoncode drwatsoncode
10 ian. 2020 06:53:09

@Dylan Este o idee bună să o faci obligatorie, dar dacă nu trimiți nonce-ul de acțiune, acesta va eșua la wp_verify_nonce care rulează înainte de executarea acțiunii

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

@drwatsoncode data.rest.nonce provine din wp_localize_script în codul exemplu

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

Bazându-ne pe ceea ce a scris @lucas-bustamante (ceea ce m-a ajutat enorm!), odată ce ai configurat header-ul X-WP-Nonce în rutele tale personalizate, poți face următoarele:

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

Reține că permission_callback este la nivelul rădăcinii, nu sub args (documentat aici) și am eliminat verificarea suplimentară a nonce din args deoarece verificarea permisiunii singură va eșua dacă nonce-ul este invalid sau nu este furnizat (am testat acest lucru extensiv și pot confirma că primesc o eroare când nu este furnizat niciun nonce sau este invalid).

26 feb. 2020 14:50:22
0

Un lucru de remarcat în legătură cu răspunsul lui @Lucas Bustamante este că procesul de verificare descris este o autentificare bazată pe utilizator. Aceasta înseamnă că dacă aveți un punct final API anonim care nu necesită un utilizator, atunci prin simpla omitere a antetului X-WP-NONCE, veți trece verificarea nonce descrisă. Furnizarea unui nonce incorect va genera totuși o eroare.

Motivul pentru aceasta este că funcția rest_cookie_check_errors, care efectuează verificarea, va seta pur și simplu current_user ca fiind gol dacă nu este furnizat niciun nonce. Acest lucru funcționează bine când este necesar un utilizator, dar nu și în caz contrar. (vezi: https://developer.wordpress.org/reference/functions/rest_cookie_check_errors/)

Dacă doriți să extindeți răspunsul lui Lucas pentru a include și puncte finale anonime, atunci puteți adăuga o verificare manuală a nonce-ului la începutul punctului dvs. final, astfel:

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