WP-API Retrieving Drafts "Cookie nonce is invalid"

10.3k views Asked by At

I'm working on a front-end app which ties into wordpress API which sits on a seperate domain.

I'm wanting to retrieve a post draft so when the user clicks "Preview Post" in the wordpress admin panel, it opens my app with the correct content.

I'm loading some Javascript into the WP Admin so I can amend all "Preview Post" links in the admin panel with a wp_nonce token for authentication purposes. This is done using the below snippet, which tweaks the preview post link into for example: http://example.com/blog?p=127&preview=true&auth=16045802ee

function admin_inline_js(){

    // Grab URL 
    echo '
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        // Grab all preview anchors
        var anchors = document.querySelectorAll("a[href*=\'preview=true\']"), i;
        // Loop through and amend to remove the trailing slash, as WP doesnt provide any easy method to achieve this.
        for(i = 0; i < anchors.length; i++) {
            anchors[i].href = anchors[i].href.replace("blog/", "blog") + \'&auth=' . wp_create_nonce('wp_rest') .'\';
        }
    });
    </script>
    ';
}
add_action( 'admin_print_scripts', 'admin_inline_js' );

At http://example.com/blog?p=127&preview=true&auth=16045802ee, the auth parameter is then used to post a request back to wordpress to retrieve the draft with an ID of 127, with a nonce token of 16045802ee. However this isn't working, and I'm getting this response:

object(stdClass)#459 (3) { ["code"]=> string(25) "rest_cookie_invalid_nonce" ["message"]=> string(23) "Cookie nonce is invalid" ["data"]=> object(stdClass)#532 (1) { ["status"]=> int(403) } }

Can anybody spot what I'm doing wrong here? :/

Thanks.

5

There are 5 answers

0
Greg On BEST ANSWER

There are two issues you are facing:

  1. A Wordpress "nonce" is a tool that is only intended to protect against CSRF tokens. Really useful when writing plugins for the admin panel, but not useful for authenticating against the API.

  2. Passing authentication details over the API is blocked by the default-prescribed .htaccess file.

You need to pass authentication details over the API in order to receive the responses containing the protected data. For simplicity, I recommend using HTTP Basic Auth to get it working first, optionally enhancing your code with more secure authentication methods such as OAuth in the future.

I recommend creating a new user specifically for API usage, rather than passing admin details around in text files on your server.

Once you have your user created, you can request the draft post using simple cURL in PHP as follows:

$apiUsername = "apiuser";
$apiPassword = "sup3r-s3cr3t-p455w0rd";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.com/wp-json/wp/v2/posts/127");
curl_setopt($ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$json = curl_exec($ch);
$postObject = json_decode($json);

This will set request header containing the username and password, and as long as nothing interferes with this header before it gets to Wordpress, you will receive your post details in $postObject and you can stop reading here. You're done.


However, for a bit of extra fun, the Wordpress-prescribed default .htaccess file drops HTTP headers, so if you're using that you'll need to fix it yourself. Basically, the Wordpress scripts need to see PHP_AUTH_USER and PHP_AUTH_PW keys within the $_SERVER variable for this to work.

A quick fix is to include the following rule as the first RewriteRule in .htaccess:

RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]

which will split the headers into variables prefixed with "REMOTE_USER", and the following PHP can be used to populate the expected fields back again (perhaps this can go in index.php).

$userPass = $_SERVER["REDIRECT_REMOTE_USER"];
if(!empty($userPass)
&& strstr($userPass, "Basic")) {
        $userPass = substr($userPass, strpos($userPass, " ") + 1);
        $userPass = base64_decode($userPass);
        $userPass = explode(":", $userPass);

        $_SERVER['PHP_AUTH_USER'] = $userPass[0];
        $_SERVER['PHP_AUTH_PW'] = $userPass[1];
}
0
zulfi Ali On

I found this Error [{"code":"json_cookie_invalid_nonce","message":"Cookie nonce is invalid"}] after I install WP POS , When the POS page opening time ERROR "forbidden"

after expand the error I get this message [{"code":"json_cookie_invalid_nonce","message":"Cookie nonce is invalid"}]

After little while I found solution. I deactivate newly installed plugins one by one , the i found problem in "Yoast SEO" plugin after deactivation it is POS plugins working fine , then again I activate Yoast SEO plugin no problem it is working fine

0
MadTurki On

You need to add the permission "promote_users" to the POS role. I used "User Role Editor" plugin to do this but you can also add the following to your functions.php:

$pos_role = get_role( 'pos' ); // Or whatever role you want to add it to
$pos_role->add_cap( 'promote_users' );
1
Rafael Keller On

I debbuged the code and think that the cookie is corrupted.

In the code snippet below, we can see.

// Check the nonce.
$result = wp_verify_nonce( $nonce, 'wp_rest' );

if ( ! $result ) {
    return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
}

For me, this issue has solved when I clear browser cookies for my domain.

1
Miguel Axcar On

Something that happened to me and can be useful: assuming that you are properly passing the auth details to API, this error can be triggered if you create the nonce with a not logged user, login in, and checking it after, and that's explained because the nonce is created using user ID.

From file: wp-includes/pluggable.php

function wp_create_nonce( $action = -1 ) {
    $user = wp_get_current_user();
    $uid  = (int) $user->ID;
    if ( ! $uid ) {
        /** This filter is documented in wp-includes/pluggable.php */
        $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    }
 
    $token = wp_get_session_token();
    $i     = wp_nonce_tick();
 
    return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
}