TL;DR - Why is my user account not logged in during an AJAX request which is made inside wp-admin?
I have the following setup:
<?php
add_action('wp_ajax_foobar_action', 'foobar_action');
add_action('wp_ajax_nopriv_foobar_action', 'foobar_action');
function foobar_action() {
check_ajax_referrer();
wp_send_json((object) ['msg' => 'hello world']);
}
add_action('admin_print_scripts', function () {
printf('<script type="text/javascript">window.custom_nonce = "%s";</script>', wp_create_nonce());
});
And in JS:
var msg = '';
// I'm using the whatwg-fetch polyfill and a polyfill for Promises.
fetch(ajaxurl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
},
body: 'action=foobar_action&_wpnonce=' + window.custom_nonce
}).then(function (res) {
msg = res.json().msg;
});
This is all run in wp-admin. The issue is that the wp-admin page/JS script itself is loaded as a logged in user, but the AJAX request that ends up into admin-ajax.php is done as a logged out guest user. This leads to the issue of wp-admin using nopriv
and check_ajax_referrer
failing 100% of the time.
Why does wp-admin make AJAX requests as a guest instead of the logged in user?
Currently, the AJAX endpoint returns 403
status with -1
content, as it should in case there is a nonce mismatch. If I comment out the check_ajax_referrer()
call then the AJAX request runs through successfully. After some furious console.log
ging I've determined that the nonce values match in the JS fetch call and the AJAX endpoint that receives it (which means the nonce is properly transferred, but a wrong guest nonce is generated during the AJAX endpoint execution.
If I remove the wp_ajax_nopriv_foobar_action
then WordPress doesn't find the auth-enabled AJAX action because there seemingly is no logged in user available (results in status 200
and body 0
).
Steps I've attempted to fix the issue:
- Logged out and in again, cleared caches/cookies, used incognito windows,
- Restarted PHP-FPM, Nginx, object cache backends, MariaDB,
- Disabled all plugins that are not required for operation,
- Moved the AJAX hooking code around in my plugin code, all the way to the plugin root file (e.g.
myplugin/myplugin.php
), - Used GET instead of POST.