Невизначене, отримане з API REST, є недійсним та відрізняється від ніколи створеного в wp_localize_script


10

Для тих, хто приїжджає з Google: Ви, мабуть, не повинні отримувати жодних питань від API REST , якщо тільки ви не знаєте, що ви робите. Cookie перевірки справжності на основі з REST API тільки призначені для плагінів і тем. Для програми на одній сторінці ви, мабуть, повинні використовувати OAuth .

Це питання існує тому, що в документації не було / не було зрозуміло, як слід насправді автентифікуватись при створенні програм для однієї сторінки, JWT не дуже підходять для веб-додатків, а OAuth важче реалізувати, ніж auth на основі файлів cookie.


У посібнику є приклад того, як клієнт Backbone JavaScript обробляє питання, і якщо я слідую за прикладом, я отримую уявлення про те, що вбудовані кінцеві точки, такі як / wp / v2 / posts, приймає.

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

]);

Однак про використання Backbone не виникає сумнівів, і такі теми є, тому я написав наступний плагін:

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

Я трохи заїхав у консоль JavaScript і написав таке:

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

Очікуваний результат - це два нові посади, але я отримую Cookie nonce is invalidз першого, а другий створює повідомлення успішно. Це, мабуть, тому, що думки різні, але чому? Я ввійшов як один і той же користувач в обох запитах.

введіть тут опис зображення

Якщо мій підхід невірний, як мені отримати поняття?

Редагувати :

Я намагався возитися з глобалами без особливої ​​удачі . Отримав трохи щасливіше, використовуючи 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,
      ];
    },
  ]);
});

Тепер, коли я запускаю JavaScript вище, створюються два повідомлення, але кінцева точка перевірки не вдається!

введіть тут опис зображення

Я перейшов до налагодження 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!

Я додав кілька журналів

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

і код JavaScript тепер призводить до наступних записів. Як ви бачите, коли викликається кінцева точка перевірки, 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

Відповіді:


3

Придивіться уважніше до function rest_cookie_check_errors().

Коли ви отримаєте нон-ценз /wp-json/nonce/v1/get, ви не надсилаєте нотатки в першу чергу. Таким чином, ця функція скасовує вашу автентифікацію за допомогою цього коду:

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

Ось чому ви отримуєте інше поняття від вашого REST-дзвінка та отримання його від теми. Виклик REST навмисно не розпізнає ваші дані для входу (у цьому випадку за допомогою файлу cookie), оскільки ви не надіслали дійсне значення у запиті на отримання.

Тепер причина вашого wp_loaded коду працювала в тому, що ви отримали поняття і зберегли його до глобального, перш ніж цей код відпочинку анулював ваш логін. Перевірка не вдається, оскільки інший код анулює ваш логін до того, як відбудеться перевірка.


Я навіть не переглядав цю функцію, але це, мабуть, має сенс. Річ у тому, чому я повинен включати дійсне невідоме запит GET? (Я розумію це зараз, але це далеко не очевидно) Вся суть / верифікувати кінцеву точку полягає в тому, що я можу перевірити, чи ніколи все-таки дійсна, і якщо вона зависла чи не є дійсною, отримати нове поняття.
Крістіан

Виходячи з джерела rest_cookie_check_errors, я повинен змінити свою кінцеву точку, щоб вона не перевіряла $_GET['nonce'], але не заголовок або $_GET['_wpnonce']параметр. Правильно?
Крістіан

1

Хоча це рішення працює, воно не рекомендується . OAuth є кращим вибором.


Я думаю, що я це отримав.

Я думаю, що wp_verify_nonce порушено, оскільки wp_get_current_user не вдається отримати належний об'єкт користувача.

Це не так, як це показав Отто.

На щастя, у нього є фільтр: $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

Використовуючи цей фільтр, я зміг написати наступне, і код JavaScript виконується так, як слід:

введіть тут опис зображення

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

Якщо ви виявите проблему безпеки з виправленням, будь ласка, дайте мені крик, зараз я не бачу нічого поганого в цьому, окрім глобальних.


0

Дивлячись на весь цей код, здається, що вашою проблемою є використання закриттів. На initетапі слід встановлювати лише гачки, а не оцінювати дані, оскільки не все ядро ​​закінчило завантаження та ініціалізацію.

В

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

це $userбуде рано використовуватись у закритті, але ніхто не обіцяє вам, що файли cookie вже були оброблені та користувач отримав автентифікацію на їх основі. Кращий код буде

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

Як завжди з будь-яким гачком у wordpress, використовуйте найновіший можливий гак і ніколи не намагайтеся перерахувати все, що вам не потрібно.


Я використав розділ "Монітори запитів" і розділ дій і гаків, щоб визначити, що виконує і в якому порядку, set_current_user працює перед init & after_setup_theme, не повинно виникнути проблем із визначенням користувачем $ зовні та перед закриттям.
Крістіан

@Christian, і всі вони можуть бути нерелевантними в контексті API json. Я був би дуже здивований, якщо монітор запитів працює в цьому контексті
Марк Каплун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.