使用 Fetch 的 WordPress REST API 身份验证

Posted

技术标签:

【中文标题】使用 Fetch 的 WordPress REST API 身份验证【英文标题】:WordPress REST API Authentication Using Fetch 【发布时间】:2018-02-22 13:20:54 【问题描述】:

我正在尝试使用Fetch API 使用cookie authentication 进行WordPress REST API 访问,但是身份验证失败并出现以下错误。

403:Cookie Nonce 无效

我正在使用以下脚本连接到 API。

const headers = new Headers(
   'Content-Type': 'application/json',
   'X-WP-Nonce': WPAPI.nonce
);  

fetch(WPAPI.root + 'my-endpoint/upload/', 
    method: 'POST',
    headers: headers,
    body: JSON.stringify(data)
)

当我从使用 Fetch 切换到 XMLHttpRequest 时,它按预期工作。

let request = new XMLHttpRequest();
request.open('POST', WPAPI.root + 'my-endpoint/upload/', true);
request.setRequestHeader('X-WP-Nonce', WPAPI.nonce);
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(data));

在 Fetch 方法中发送标头的方式是否可能存在问题?

【问题讨论】:

【参考方案1】:

晚了,但可能对其他读者有帮助,因为我根据这个问题专门为 fetch() 承诺添加了代码。

我发现,WordPress 自动在其 cookie 中使用 nonce。

WordPress:5.7.2 版PHP:7.4 版主机:hostmonster.com客户端:Windows 10浏览器:在 Chrome、Firefox、甚至 Edge 上测试 ? 工作

代码(您安装的主题的function.php中的PHP代码):

add_action('rest_api_init', function() 
    /**
     * Register here your custom routes for your CRUD functions
     */
    register_rest_route( 'my-endpoint/v1', '/upload/', array(
        array(
            'methods'  => WP_REST_Server::READABLE, // = 'GET'
            'callback' => 'get_data',
            // Always allow, as an example
            'permission_callback' => '__return_true'
        ),
        array(
            'methods'  => WP_REST_Server::CREATABLE, // = 'POST'
            'callback' => 'create_data',
            // Here we register our permissions callback
            // The callback is fired before the main callback to check if the current user can access the endpoint
            'permission_callback' => 'prefix_get_private_data_permissions_check',
        ),
    ));
);

// The missing part:
// Add your Permission Callback function here, that checks for the cookie
// You should define your own 'prefix_' name, though

function prefix_get_private_data_permissions_check() 
    
    // Option 1: Password Protected post or page:
    // Restrict endpoint to browsers that have the wp-postpass_ cookie.
    if ( !isset($_COOKIE['wp-postpass_'. COOKIEHASH] )) 
        return new WP_Error( 'rest_forbidden', esc_html__( 'OMG you can not create or edit private data.', 'my-text-domain' ), array( 'status' => 401 ) );
    ;

    // Option 2: Authentication based on logged-in user:
    // Restrict endpoint to only users who have the edit_posts capability.
    if ( ! current_user_can( 'edit_posts' ) ) 
        return new WP_Error( 'rest_forbidden', esc_html__( 'OMG you can not create or edit private data.', 'my-text-domain' ), array( 'status' => 401 ) );
    ;
 
    // This is a black-listing approach. You could alternatively do this via white-listing, by returning false here and changing the permissions check.
    return true;
;

function create_data() 
    global $wpdb;

    $result = $wpdb->query(...);

    return $result;


function get_data() 
    global $wpdb;

    $data = $wpdb->get_results('SELECT * from `data`');

    return $data;

确保在您的 HTTP 请求中包含您的 HTML 页面 credentials: 'same-origin',如上面之前的答案和 cmets 中正确说明的那样。

代码HTML内嵌<script> ... </script>):

<script>

// Here comes the REST API part:
// HTTP requests with fetch() promises

function getYourData() 
  let url = 'https://example.com/wp-json/my-endpoint/v1/upload/';
  fetch(url, 
    method: 'GET',
    credentials: 'same-origin', // <-- make sure to include credentials
    headers:
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        //'Authorization': 'Bearer ' + token  <-- not needed, WP does not check for it
    
  ).then(res => res.json())
  .then(response => get_success(response))
  .catch(error => failure(error));
;

function insertYourData(data) 
  let url = 'https://example.com/wp-json/my-endpoint/v1/upload/';
  fetch(url, 
    method: 'POST',
    credentials: 'same-origin', // <-- make sure to include credentials
    headers:
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        //'Authorization': 'Bearer ' + token  <-- not needed, WP does not check for it
    ,
    body: JSON.stringify(data)
  ).then(res => res.json())
  .then(response => create_success(response))
  .catch(error => failure(error));
;

// your Success and Failure-functions:

function get_success(json) 
  // do something here with your returned data ....
  console.log(json);
;

function create_success(json) 
  // do something here with your returned data ....
  console.log(json);
;

function failure(error) 
  // do something here ....
  console.log("Error: " + error);
;

</script>

最后的想法:

'Authorization': 'Bearer ' + token 必须在 HTTP 请求的头部吗?

经过一些测试,我意识到 Permission Callback 中的if ( !isset($_COOKIE['wp-postpass_'. COOKIEHASH] )) ... 不仅检查 Cookie 是否设置在客户端浏览器上,而且似乎还检查了它的值(JWT令牌)

因为我像使用初始代码一样进行检查,传递了一个错误的令牌,消除了 cookie,或者让会话保持打开状态,但在后端更改了站点的密码(因此 WordPress 会创建一个新的令牌,因此设置的值wp_postpass_ cookie 会改变)并且所有测试都正确 - REST API 被阻止,不仅验证 cookie 的存在,而且验证它的价值(这很好 - 感谢 WordPress 团队)。

来源: 我在FAQ section 中找到了有关上述想法的以下资源:

Why is the REST API not verifying the incoming Origin header? Does this expose my site to CSRF attacks?

因为 WordPress REST API 不验证 Origin 标头 传入请求,因此可以访问公共 REST API 端点 从任何网站。这是一个有意的设计决策。

然而,WordPress 有一个现有的 CSRF 保护机制,它 使用随机数。

根据我目前的测试,WP 身份验证方式运行良好

为 WordPress 团队点赞?

来自 WordPress REST API 手册的另外 2 个来源:

REST API Handbook / Extending the REST API / Routes and EndpointsREST API Handbook / Extending the REST API / Adding Custom Endpoints

以及关于rest_cookie_check_errors()函数的WordPress 代码参考的1个源代码:

Reference / Functions / rest_cookie_check_errors()

对于那些对我的发现的完整故事感兴趣的人,请点击我的帖子链接,其中包含答案、代码 sn-ps 和其他发现。

How to force Authentication on REST API for Password protected page using custom table and fetch() without Plugin

【讨论】:

【参考方案2】:

在 4 年前看到我的帖子,寻找相同的问题 :) 这样就解决了问题。

const response = await fetch(url, 
    method: 'POST',
    credentials: 'same-origin',
    headers: 
        'Content-Type': 'application/json',
        'X-WP-Nonce' : my_var.nonce
    ,
    body: JSON.stringify(data),
);
const content = await response.json();
console.log(content);

【讨论】:

【参考方案3】:

WordPress nonce 身份验证需要使用 cookie,默认情况下 Fetch 不会发送这些。您可以使用凭据选项来完成这项工作:

fetch(endpoint, 
  credentials: 'same-origin'
)

https://github.com/github/fetch#sending-cookies

【讨论】:

以上是关于使用 Fetch 的 WordPress REST API 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

更简洁/优雅的方式来连接 fetch() 返回的 JSON 元素

Wordpress 在使用 fetch API 的 ajax 调用上返回 400 Bad Request

Firebase REST API 不使用 React Native 上的 Fetch API 响应常见错误代码

javascript REST httpRequest&Fetch

当邮递员正在做时,javascript fetch 没有发布到 rest api 端点

如何使用 REST_API wordpress 创建用户?