1

My wordpress site sits behind Akamai, which is a cacheing service similar to Cloudflare.

I make the following API call:

GET /wp-json/mytheme/v1/get-posts?post_type=videos

This is done using apiFetch from '@wordpress/api-fetch';

And it automatically includes this in the request header

X-WP-Nonce: 12323423

This works fine until 24 hours later, when the nonce expires. The cache still continues to use the expired Nonce resulting in a 403 forbidden and a broken page.

If I make the same request without Nonce header, it works perfectly fine.

Is there a way in Wordpress to disable or remove the Nonce for GET requests only?

Or even strip out the X-WP-Nonce header by intercepting the Request?

This my code for making the request which is being made from the wordpress frontend.

apiFetch({
     path: '/wp-json/mytheme/v1/get-posts?post_type=videos',
     parse: false,
});
4
  • If you provide a valid nonce in your request, WordPress will send a refreshed nonce in the response header (X-WP-Nonce), so you can use it on your next requests.
    – Sally CJ
    Jun 26, 2019 at 7:40
  • Only if you supplied a valid nonce in the first place, which I don't in this case.But that is good to know, thanks.
    – elMarquis
    Jun 26, 2019 at 23:21
  • There are several options that you can choose from, but from where are you making the API calls - on the same WordPress site, on the front-end or admin? Can I see your code?
    – Sally CJ
    Jun 27, 2019 at 4:53
  • Have updated the question to show code sample.
    – elMarquis
    Jun 27, 2019 at 7:22

3 Answers 3

4

Based on the authentication documentation here - a nonce key needs to be passed with each request.

So if the nonce key is being cached on the frontend beyond its lifespan, you will need to hook into the API request before the authentication step and replace the cached nonce key with a valid one.

WordPress provides a rest_send_nocache_headers filter for us to hook into (See here). This lets us perform an action before the authentication.

$send_no_cache_headers = apply_filters('rest_send_nocache_headers', is_user_logged_in());
if (!$send_no_cache_headers && !is_admin() && $_SERVER['REQUEST_METHOD'] == 'GET') {
    $nonce = wp_create_nonce('wp_rest');
    $_SERVER['HTTP_X_WP_NONCE'] = $nonce;
}

In the above example, we hook into the filter passing the is_user_logged_in() function as the parameter. This will return true or false.

Then in our query, if the user is not logged in, they are not in the admin and, this is a GET request we proceed with switching the invalid nonce key with a valid one.

2
  • Can confirm, this is working. Thank you.
    – elMarquis
    Jun 28, 2019 at 4:55
  • Hi, I've got the same problem : the call to api with GET method, get a 403 code because of expiring lifespan on front-end (for both logged and unlogged users). But I don't understand where I'm suppose to put that code : in functions.php ? in the custom rest controller (in register_routes or in callbacj functions) ? Thanks
    – CRavon
    May 14, 2020 at 14:41
2

Just to add to the accepted answer, I found a similar solution but instead hooked into rest_authentication_errors prior to rest_cookie_check_errors running.

Since the underlying issue is nonce expiration, it's possible you could have this problem when the user is logged in (i.e. when no cache headers are sent) as well as when logged out. I also put some checks in to ensure we're dealing with just our REST request - I checked the 'rest_route' query var but there may be a better way of doing this.

add_filter( 'rest_authentication_errors', function( $errors ) {
        // Bail if rest_route isn't defined (shouldn't happen!)
        if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
            return $errors;
        }

        $route = ltrim( $GLOBALS['wp']->query_vars['rest_route'], '/' );

        // Ensure we're dealing with our REST requst.
        if ( 0 !== strpos( $route, 'my-awesome-namespace/v1' ) ) {
            return $errors;
        }

        if ( ! empty( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
            $nonce = $_SERVER['HTTP_X_WP_NONCE'];

            if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
                // Nonce check failed, so create a new one.
                $_SERVER['HTTP_X_WP_NONCE'] = wp_create_nonce( 'wp_rest' );
            }
        }

        return $errors;
    }, 10 );
0

I had the same problem, but my solution was to just switch to native fetch instead of using WordPress fetch:

const response = await fetch('/wp-json/mytheme/v1/get-posts?post_type=videos');
const data = await response.json();

Native fetch does not transfer a nonce, so the problem is solved with this - unless you really need a nonce, in that case it is better to use WP Fetch instead, of course :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.