Deprecated: preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in /data/sites/web/remonpelnl/www/wp-content/themes/remonpel_nl__child__twentyfourteen/functions.php on line 32
WordPress REST-API nonce-sense. – Remons TechNotes

WordPress REST-API nonce-sense.

Working with the WordPress REST-API is HELL. There. I said it. It is powerful, it is secure, it is everything a developer needs, but for the love of [fill in your favorite deity here], WordPress, be consistent!

Using the REST-API requires authentication. Well, that’s not a problem. Just create a route to log-in and one to log-out. WordPress has functions to do that.

wp_signon()
and
wp_logout()

The first hurdle is getting the WordPress REST API to function. Oh, wait, you need a nonce ?! Well, thank you WordPress for this ‘security’-measure. For everything else in WordPress the authentication cookies you get when logging in to /wp-admin are enough, but for REST-API you need a nonce … the F why !?

Sorry, but this is just NONCE-SENSE! Pun intented. If only it were funny.

To get nonces to actually work, you need something like this:
(over simplified, please make sure you do this un-hackable ^^)

Somewhere in PHP

<?php add_action('wp_head', function(){
print '<script>window.nonce = '. json_encode(wp_create_nonce( 'wp_rest' )) .'</script>';
}); ?>

and in your application.js

$(document).ajaxSend(function (event, xhr, settings) {
xhr.setRequestHeader('X-WP-Nonce', window.nonce);
});

Now you can send the nonce to WordPress. Great. But then it works once you are already logged in or not. It stops working on a change of log-in status. After log-in, the nonce is no longer valid and a new nonce is needed. Same after log-out.

Simple solution: in your REST response, send a new nonce, and update the nonce-variable in javascript accordingly.

Somewhere in PHP

<?php add_filter( 'rest_post_dispatch', function( WP_REST_Response $response) {
$response->header('X-WP-Nonce', wp_create_nonce( 'wp_rest' )); return $response;
}, PHP_INT_MAX);?>

and in your application.js

$(document).ajaxComplete(function (event, xhr, settings) {
window.nonce = xhr.getResponseHeader('X-WP-Nonce');
});

But wait; just generating a new nonce will not work!

Why not?

on wp_signon(), WordPress sets new cookies. but WordPress only uses set_cookie() to do that. In other words; the new cookies are valid only on consequent page loads.
wp_create_nonce() uses the cookie-values in memory (PHP $_COOKIE) which … is NOT refreshed after log-in.
wp_logout() has the same issue. WordPress sends empty cookies, but does not update the local memory, again, requiring a page reload.

Solution:

Do what WordPress should do, but doesn’t.

<?php
add_action('set_logged_in_cookie', function($cookie_value){
$_COOKIE[ LOGGED_IN_COOKIE ] = $cookie_value;
}, PHP_INT_MAX); add_action('clear_auth_cookie', function(){
$_COOKIE[ LOGGED_IN_COOKIE ] = ' ';
});

With this, the used $_COOKIE variable is now identical to the cookie.

But this does not work… yet…

Why?

Well, wp_create_nonce() depends on more than just the cookie. It depends on the current-user also. The global variable $current_user holds the current user. So it would be safe to assume that on wp_signon(), that variable is updated, right? Well that assumption would be wrong!

wp_signon() does NOT set the global user object to the new user. And, failing consistently, wp_logout() does NOT invalidate the global user object.

Again, more hooks just to make WordPress do what it should do out of the box;

<?php
add_action('wp_login', function($login, $user){
wp_set_current_user( $user->ID );
}, PHP_INT_MAX, 2); add_action('wp_logout', function(){
wp_set_current_user( 0 );
}, PHP_INT_MAX);

The full code for your enjoyment:

P.s. I repeat: this code is perhaps not secure, perhaps over-simplified, but at least, with this, the REST-API is usable. The alternative is reverting to 1990s AJaX calls through wp-admin/admin-ajax.php.

Author: Remon Pel

WebDeveloper though not WebDesigner

9 thoughts on “WordPress REST-API nonce-sense.”

  1. Fantastic! I’ve not touched wordpress in… 10 or so years… I see it has a REST api… so thought I’d use it as a backend for an SPA…

    This whole nonce thing has been twisting my melon for the last 3 days. I’ve been banging my head against the wall (made worse because I’m terrible at PHP). Log in and log out just didn’t work.. subsequent ajax posts were failing. I couldn’t understand it… I just assumed it would work as your post described it should…

    Thank you for this code – I’ve dropped it in, and my REST api and SPA are talking to each other… thank you! Hugely helpful! :)

  2. I am struggling to implement refreshing nonces when using with REST API. Should I set the nonce_life to 30-90 days? As setting it 1 day will require user to login again and again.

    1. A nonce is only used to validate a form submission, to prevent spamming a form etc. It has no influence over the login lifetime. Nonces typically have a lifetime of 5 minutes, not hours and certainly not days or months.
      I’m guessing you have other issues in your login process, but unfortunately, this is not the place for that as I am only one person; you will be better off on one of the many forums out there, like https://WordPress.stackexchange.com or https://wordpress.org/support.

      My example above works (or at least worked, when it was written) to refresh the nonce after login or logout, but, as said, does not change anything for how long a user is logged in or the automatic logout process (of which I never knew there was).

      Maybe it’s as simple as the login-cookies not being set properly? Again; try the power of the hive-mind ;)

      Good luck!

  3. By automatic logout, I meant if a user sets remember to true then the auth cookie is only valid for 14 days, which I extended using the ‘auth_cookie_expiration’ filter.

    WP Nonce documentation is quite tricky to understand.

    After trying your solution, I realised that new nonce gets generated after the half of its life i.e if nonce life is 24hrs and then we use the wp_create_nonce() after 12 hrs, it will generate a new nonce and the old one will still be valid till the completion of 24hrs from its generation time.

    To simplify the flow,
    whenever the REST API throws the ‘rest_cookie_invalid_nonce’ error we will already have the new Nonce in the response header of that error. So we just need to pass new Nonce and continue the GET/POST requests.
    Right?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Confidental Infomation
stop spam mail