Функция wp_get_current_user() не работает в callback-функции REST API
Рассмотрим следующий класс.
<?php
class MCQAcademy_Endpoint extends WP_REST_Controller {
/**
* Регистрирует маршруты для объектов контроллера.
*/
public function register_routes() {
$version = '1';
$namespace = 'custompath/v' . $version;
$base = 'endpointbase';
register_rest_route(
$namespace,
'/' . $base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(),
)
)
);
}
/**
*
*/
public function get_items( $request ) {
$rs = array(
'data' => array(),
'request' => array(
'lang' => 'en',
),
);
$args = array();
$items = get_posts( $args );
foreach( $items as $item ) {
$itemdata = $this->prepare_item_for_response( $item, $request );
$rs['data'][] = $this->prepare_response_for_collection( $itemdata );
}
$rs['wp_get_current_user'] = wp_get_current_user(); // Не возвращает ожидаемый результат
return new WP_REST_Response( $rs, 200 );
}
/**
* Проверяет права доступа для получения элементов
*/
public function get_items_permissions_check( $request ) {
return true; // сделано общедоступным
}
/**
* Подготавливает элемент для операций создания или обновления
*/
protected function prepare_item_for_database( $request ) {
return $request;
}
/**
* Подготавливает элемент для REST-ответа
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'ID' => $item->ID,
'post_content' => wpautop($item->post_content),
'post_title' => $item->post_title,
);
return $data;
}
/**
* Получает параметры запроса для коллекций
*/
public function get_collection_params() {
return array(
'page' => array(
'description' => 'Текущая страница коллекции.',
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
),
'per_page' => array(
'description' => 'Максимальное количество элементов в результирующем наборе.',
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
),
'search' => array(
'description' => 'Ограничить результаты теми, что соответствуют строке.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
// Регистрируем наш REST-сервер
public function hook_rest_server(){
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
}
$myEndpoint = new MCQAcademy_Endpoint();
$myEndpoint->hook_rest_server();
Всё работает корректно, за исключением вызова функции wp_get_current_user() в функции get_items(), которая возвращает пустого пользователя, даже если пользователь авторизован на сайте.
Тот факт, что пользователь вошел на ваш сайт, не означает, что он аутентифицирован для запросов к REST API. Именно поэтому вы получаете некорректного пользователя или Id = 0.
Рекомендуем ознакомиться с методами аутентификации REST API в документации:
https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/
Для разработчиков, выполняющих ручные Ajax-запросы, необходимо передавать nonce с каждым запросом. API использует nonce с действием, установленным в wp_rest. Их можно передать в API через параметр данных _wpnonce (либо в POST-данных, либо в запросе для GET-запросов) или через заголовок X-WP-Nonce. Если nonce не предоставлен, API установит текущего пользователя в 0, превратив запрос в неаутентифицированный, даже если вы вошли в WordPress.
Для удаленной аутентификации рекомендуем плагин JWT для быстрого старта:
Или вы можете использовать варианты, предложенные в документации:
Мне нужен идентификатор пользователя, если он авторизован на сайте. Поскольку конечная точка открыта, проверка прав доступа не требуется.
Shah Alom
Эта строка всегда возвращает false, даже если пользователь авторизован на сайте.
$rs['wp_get_current_user'] = is_user_logged_in() ? get_current_user_id() : false;
Shah Alom
Привет @ShahAlom, как я уже упоминал в своем ответе, авторизация на сайте отличается от авторизации в REST API. Пожалуйста, ознакомьтесь с методами аутентификации REST API в документации: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/
Pabamato
Для аутентификации с помощью cookies: разработчикам, выполняющим ручные Ajax-запросы, необходимо передавать nonce с каждым запросом. API использует nonce с действием, установленным в wp_rest. Затем их можно передать в API через параметр данных _wpnonce (либо в POST-данных, либо в запросе для GET-запросов) или через заголовок X-WP-Nonce. Если nonce не предоставлен, API установит текущего пользователя в 0, превращая запрос в неаутентифицированный, даже если вы вошли в WordPress.
Pabamato
Пожалуйста, обновите ваш ответ с этим последним комментарием, чтобы я мог принять его как решение.
Shah Alom
Даже с аутентификацией JWT вам все равно нужно передавать заголовок X-WP-Nonce для получения текущего пользователя. Например, если вам нужно получить доступ к users/me, все равно возвращается 401, то же самое для запроса пользователей по роли и так далее. "Не ручные" AJAX-запросы являются "особыми" только потому, что официальный клиент API включает этот заголовок, но ничего больше.
Jesús Franco
@JesúsFranco по моему опыту использования плагина JWT, нет необходимости отправлять какие-либо дополнительные заголовки, достаточно только заголовка Authorization с ранее сгенерированным токеном для аутентификации. /wp/v2/users/me работает у меня
Pabamato
Хмм, это заставляет задуматься, что может отличаться в разных настройках - версия плагина? Версия ядра?
Jesús Franco
Размышляя о возможных различиях, я заметил два типа запросов, которые запрещались без использования nonce, и также разрешались без него. Разница, которую я заметил, заключается в среде выполнения запроса. Запросы через PHP-скрипт разрешаются при наличии только токена, а запросы через браузер - нет, без nonce.
Jesús Franco
@JesúsFranco вы правы, для запросов на стороне сервера (как в моем случае) токена будет достаточно, для клиентских/ручных ajax-запросов необходимо включать nonce для защиты от CSRF, нет необходимости включать токен, так как у вас уже есть авторизованный пользователь и клиентская часть работает на основе cookies
Pabamato
Если API-вызов поступает без установленного одноразового кода (nonce), WordPress деаутентифицирует запрос, удаляя текущего пользователя и делая запрос неаутентифицированным. Эта защита от CSFR (межсайтовой подделки запроса) работает автоматически, и вам не нужно ничего делать для её работы, кроме как включить одноразовый код.
Решение заключается в добавлении одноразового кода. Например, так:
$_wpnonce = wp_create_nonce( 'wp_rest' );
echo "<input type = 'hidden' name = '_wpnonce' value = '$_wpnonce' />";
И затем включить его в ваш API-запрос. Одноразовый код должен называться "_wpnonce", а действие должно быть "wp_rest" для корректной работы API. Его можно передать в URL или в теле POST-запроса.
Вот полностью рабочий пример кода для получения ID текущего пользователя через REST API.
my-plugin.php
class MyPlugin {
public function __construct() {
add_action('wp_enqueue_scripts', [$this, 'scripts']);
add_action('rest_api_init', [$this, 'rest']);
}
function scripts() {
// Подключаем JS
wp_enqueue_script('my-plugin', plugin_dir_url(__FILE__) . 'js/scripts.js', ['jquery'], NULL, TRUE);
// Передаём nonce в JS
wp_localize_script('my-plugin', 'MyPluginSettings', [
'nonce' => wp_create_nonce('wp_rest'),
]);
}
function rest() {
// Регистрируем маршрут
register_rest_route('my-plugin/v1', '/uid', [
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'rest_callback'],
]);
}
function rest_callback($data) {
// Получаем ID текущего пользователя
$data = [
'uid' => get_current_user_id(),
];
$response = new WP_REST_Response($data, 200);
// Устанавливаем заголовки
$response->set_headers(['Cache-Control' => 'must-revalidate, no-cache, no-store, private']);
return $response;
}
}
new MyPlugin();
js/scripts.js
(function($) {
$(document).ready(function() {
var settings = MyPluginSettings;
$.ajax({
url: '/wp-json/my-plugin/v1/uid',
method: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('X-WP-Nonce', settings.nonce);
}
}).done(function(response) {
// Вернёт ваш UID
console.log(response);
});
});
})(jQuery);
Источник: https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/
Я не понимаю, как 'MyPluginSettings' передается в скрипт. Возможные моменты для рассмотрения: в настоящее время я не использую JQuery, а "чистый" JavaScript ... Я вставлял свой скрипт в HTML-теги <script>, когда это было нужно, но попробовал подключать их через wp_enqueue_script и столкнулся с той же проблемой.
TTT
У меня получилось это заработать. Я указал неправильный путь при адаптации для wp_enqueue_script(). Также у меня было несколько скриптов, и я только что понял, что первый параметр wp_enqueue_script() относится не конкретно к плагину, а к скрипту (возможно, вам стоит заменить 'my-plugin' в вашем ответе, потому что это вводит в заблуждение).
TTT
wp_get_current_user() не работает, потому что ваш пользователь не установлен корректно. Для справки смотрите код ниже в /wp-includes/user.php.
if ( ! empty( $current_user ) ) {
if ( $current_user instanceof WP_User ) {
return $current_user;
}
// Обновление stdClass до WP_User
if ( is_object( $current_user ) && isset( $current_user->ID ) ) {
$cur_id = $current_user->ID;
$current_user = null;
wp_set_current_user( $cur_id );
return $current_user;
}
// $current_user содержит некорректное значение. Принудительно устанавливаем WP_User с ID 0.
$current_user = null;
wp_set_current_user( 0 );
return $current_user;
}
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
wp_set_current_user( 0 );
return $current_user;
}
Всё, что вам нужно сделать — это вызвать wp_set_current_user( {user_id} ) в начале вашего API.