O Nonce recuperado da API REST é inválido e diferente do nonce gerado em wp_localize_script

8

Para aqueles que chegam do Google: você provavelmente não deve obter as exceções de a API REST , a menos que você realmente saiba o que está fazendo. Autenticação baseada em cookie com a API REST é apenas significado para plugins e temas. Para um aplicativo de página única, você provavelmente deve usar o OAuth .

Essa pergunta existe porque a documentação não é clara sobre como você deve autenticar ao criar aplicativos de página única, as JWTs não são realmente adequadas para aplicativos da web e o OAuth é mais difícil de implementar do que a autenticação baseada em cookies.

O manual tem um exemplo de como o cliente de backbone JavaScript manipula nonces, e se eu seguir o exemplo, eu recebo um nonce que o construído em endpoints como / wp / v2 / posts aceita.

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

]);

No entanto, o uso do Backbone está fora de questão, assim como os temas, então eu escrevi o seguinte 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,
      ];
    },
  ]);
});

Eu mexi um pouco no console do JavaScript e escrevi o seguinte:

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

O resultado esperado é de dois novos posts, mas eu recebo Cookie nonce is invalid do primeiro, e o segundo cria o post com sucesso. Isso é provavelmente porque as nórdicas são diferentes, mas por quê? Estou logado como o mesmo usuário em ambas as solicitações.

Seminhaabordagemestáerrada,comodevoobterononce?

Editar:

Eutenteibrincarcomoglobalsemmuitasorte.Ficouumpoucomaissortudoaoutilizaraaçãowp_loaded:

<?php/*PluginName:NonceEndpoint*/$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,
      ];
    },
  ]);
});

Agora, quando executo o JavaScript acima, duas postagens são criadas, mas o endpoint de verificação falha!

Eufuidepurarowp_verify_nonce:

functionwp_verify_nonce($nonce,$action=-1){$nonce=(string)$nonce;$user=wp_get_current_user();$uid=(int)$user->ID;//Thisis0,eventhoughtheverifyendpointsaysI'mloggedinasuser2!

Euadicioneialgunsregistros

//Noncegenerated0-12hoursago$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 o código JavaScript agora resulta nas seguintes entradas. Como você pode ver, quando o endpoint de verificação é chamado, o 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
    
por Christian 28.02.2018 / 17:43

3 respostas

3

Veja mais de perto o function rest_cookie_check_errors() .

Quando você recebe o nonce via /wp-json/nonce/v1/get , você não está enviando um nonce em primeiro lugar. Então essa função anula sua autenticação, com este código:

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

É por isso que você está ganhando um número diferente de sua chamada REST vs. recebendo do tema. A chamada REST intencionalmente não está reconhecendo suas credenciais de login (neste caso, via cookie auth) porque você não enviou um nonce válido na solicitação get.

Agora, o motivo pelo qual seu código wp_loaded funcionou foi porque você obteve o nonce e o salvou em um global antes que esse código de reserva anulasse seu login. A verificação falha porque o código de descanso anula seu login antes que a verificação ocorra.

    
por Otto 01.03.2018 / 15:00
1

Embora esta solução funcione, não é recomendado . OAuth é a escolha preferida.

Eu acho que entendi.

Eu acho que wp_verify_nonce está quebrado, pois wp_get_current_user falha ao obter o objeto de usuário apropriado.

Não é, como ilustrado por Otto.

Por sorte, ele tem um filtro: $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

Usando esse filtro, consegui escrever o seguinte, e o código JavaScript foi executado da seguinte forma:

<?php/*PluginName:NonceEndpoint*/$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 você encontrar um problema de segurança com a correção, por favor, dê-me um grito, agora eu não consigo ver nada de errado com isso, além de globals.

    
por Christian 01.03.2018 / 12:58
0

Olhando para todo esse código, parece que o seu problema é o uso de closures. No estágio init , você deve configurar os ganchos e não avaliar os dados, pois nem todo o núcleo terminou de carregar e ser inicializado.

Em

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

o $user é vinculado antecipadamente para ser usado no encerramento, mas ninguém promete a você que o cookie já foi manipulado e que um usuário foi autenticado com base neles. Um código melhor será

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

Como sempre, com qualquer gancho no wordpress, use o gancho mais recente possível e nunca tente pré-calcular nada que você não precise.

    
por Mark Kaplun 01.03.2018 / 13:37