Il nonce recuperato dall'API REST non è valido e diverso dal nonce generato in wp_localize_script


10

Per quelli che arrivano da Google: probabilmente non dovresti ottenere i nonces dall'API REST , a meno che tu non sappia davvero cosa stai facendo. L'autenticazione basata su cookie con l'API REST è pensata solo per plugin e temi. Per un'applicazione a pagina singola, dovresti probabilmente usare OAuth .

Questa domanda esiste perché la documentazione non è / non era chiara su come dovresti effettivamente autenticarti durante la creazione di app a pagina singola, i JWT non sono davvero adatti per le app Web e OAuth è più difficile da implementare rispetto all'autenticazione basata su cookie.


Il manuale ha un esempio su come il client Backbone JavaScript gestisce i nonces e, se seguo l'esempio, ottengo un nonce che accetta gli endpoint integrati come / wp / v2 / posts.

\wp_localize_script("client-js", "theme", [
  'nonce' => wp_create_nonce('wp_rest'),
  'user' => get_current_user_id(),

]);

Tuttavia, l'utilizzo di Backbone è fuori discussione, così come lo sono i temi, quindi ho scritto il seguente plugin:

<?php
/*
Plugin Name: Nonce Endpoint
*/

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => wp_create_nonce('wp_rest'),
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Ho armeggiato un po 'nella console JavaScript e ho scritto quanto segue:

var main = async () => { // var because it can be redefined
  const nonceReq = await fetch('/wp-json/nonce/v1/get', { credentials: 'include' })
  const nonceResp = await nonceReq.json()
  const nonceValidReq = await fetch(`/wp-json/nonce/v1/verify?nonce=${nonceResp.nonce}`, { credentials: 'include' })
  const nonceValidResp = await nonceValidReq.json()
  const addPost = (nonce) => fetch('/wp-json/wp/v2/posts', {
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify({
      title: `Test ${Date.now()}`,
      content: 'Test',
    }),
    headers: {
      'X-WP-Nonce': nonce,
      'content-type': 'application/json'
    },
  }).then(r => r.json()).then(console.log)

  console.log(nonceResp.nonce, nonceResp.user, nonceValidResp)
  console.log(theme.nonce, theme.user)
  addPost(nonceResp.nonce)
  addPost(theme.nonce)
}

main()

Il risultato atteso sono due nuovi post, ma ottengo Cookie nonce is invaliddal primo e il secondo crea il post con successo. Questo probabilmente perché i nonci sono diversi, ma perché? Ho effettuato l'accesso come lo stesso utente in entrambe le richieste.

inserisci qui la descrizione dell'immagine

Se il mio approccio è sbagliato, come devo ottenere il nonce?

Modifica :

Ho provato a fare scherzi con i giocatori senza molta fortuna . Sono diventato un po 'più fortunato utilizzando l'azione wp_loaded:

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      error_log("verify $nonce $user");
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Ora quando eseguo JavaScript sopra, vengono creati due post, ma l'endpoint di verifica ha esito negativo!

inserisci qui la descrizione dell'immagine

Sono andato al debug wp_verify_nonce:

function wp_verify_nonce( $nonce, $action = -1 ) {
  $nonce = (string) $nonce;
  $user = wp_get_current_user();
  $uid = (int) $user->ID; // This is 0, even though the verify endpoint says I'm logged in as user 2!

Ho aggiunto un po 'di registrazione

// Nonce generated 0-12 hours ago
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
error_log("expected 1 $expected received $nonce uid $uid action $action");
if ( hash_equals( $expected, $nonce ) ) {
  return 1;
}

// Nonce generated 12-24 hours ago
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
error_log("expected 2 $expected received $nonce uid $uid action $action");
if ( hash_equals( $expected, $nonce ) ) {
  return 2;
}

e il codice JavaScript ora risulta nelle seguenti voci. Come puoi vedere, quando viene chiamato l'endpoint di verifica, uid è 0.

[01-Mar-2018 11:41:57 UTC] verify 716087f772 2
[01-Mar-2018 11:41:57 UTC] expected 1 b35fa18521 received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:57 UTC] expected 2 dd35d95cbd received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest

Risposte:


3

Dai un'occhiata più da vicino al function rest_cookie_check_errors().

Quando ricevi il nonce tramite /wp-json/nonce/v1/get, non stai inviando un nonce in primo luogo. Quindi questa funzione annulla la tua autenticazione, con questo codice:

if ( null === $nonce ) {
    // No nonce at all, so act as if it's an unauthenticated request.
    wp_set_current_user( 0 );
    return true;
}

Ecco perché stai ricevendo un nonce diverso dalla tua chiamata REST contro ottenerlo dal tema. La chiamata REST non sta intenzionalmente riconoscendo le credenziali di accesso (in questo caso tramite l'autenticazione del cookie) perché non hai inviato un nonce valido nella richiesta get.

Ora, il motivo per cui il tuo codice wp_loaded ha funzionato era perché hai ottenuto il nonce e l'hai salvato su un globale prima che questo codice di riposo annullasse il tuo login. La verifica ha esito negativo perché il codice di riposo annulla l'accesso dell'utente prima che abbia luogo la verifica.


Non ho nemmeno guardato quella funzione, ma probabilmente ha senso. Il fatto è, perché dovrei includere un nonce valido per la richiesta GET? (Lo capisco ora, ma è tutt'altro che ovvio) L'intero punto dell'endpoint / verifica è che posso verificare se il nonce è ancora valido e se sta diventando obsoleto o non è valido, ottenere un nuovo nonce.
Christian

In base alla fonte di rest_cookie_check_errors, dovrei cambiare il mio endpoint in modo che non controlli $_GET['nonce'], ma l'intestazione o il $_GET['_wpnonce']parametro nonce . Corretta?
Christian

1

Mentre questa soluzione funziona, non è consigliata . OAuth è la scelta preferita.


Penso di averlo capito.

Io penso che wp_verify_nonce è rotto, come wp_get_current_user non riesce a ottenere l'oggetto utente corretto.

Non lo è, come illustrato da Otto.

Fortunatamente ha un filtro: $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

Utilizzando questo filtro sono stato in grado di scrivere quanto segue e il codice JavaScript viene eseguito come dovrebbe:

inserisci qui la descrizione dell'immagine

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      add_filter("nonce_user_logged_out", function ($uid, $action) use ($user) {
        if ($uid === 0 && $action === 'wp_rest') {
          return $user;
        }

        return $uid;
      }, 10, 2);

      return [
        'status' => wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Se riscontri un problema di sicurezza con la correzione, per favore fammi un grido, in questo momento non riesco a vedere nulla di sbagliato, a parte i globi.


0

Guardando tutto questo codice sembra che il tuo problema sia l'uso delle chiusure. A questo initpunto dovresti solo impostare hook e non valutare i dati poiché non tutti i core avevano terminato il caricamento e l'inizializzazione.

In

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

l' $userè destinata presto per essere utilizzati nella chiusura, ma nessuno promesse a voi che il cookie sono stati già trattati e un utente è stato autenticato basata su di essi. Sarà un codice migliore

add_action('rest_api_init', function () {
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () {
    $user = get_current_user_id();
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

Come sempre con qualsiasi hook di wordpress, usa l'ultimo hook possibile e non provare mai a precalcolare tutto ciò che non è necessario.


Ho usato la sezione Azioni e hook di Monitoraggio query per capire che cosa viene eseguito e in quale ordine, set_current_user viene eseguito prima di init e after_setup_theme, non dovrebbe esserci un problema con $ user che viene definito all'esterno e prima delle chiusure.
Christian

@Christian, e tutti potrebbero non essere rilevanti nel contesto dell'API json. Sarei molto sorpreso se il monitoraggio delle query funzionasse in quel contesto
Mark Kaplun
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.