Verificarea nonce în REST API
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?)

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

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

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

@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

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).

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