0

I want to only permit the execution of specific admin tasks for users who are

  1. currently logged in to the wp admin panel (via is_user_logged_in) and who
  2. have a specific capability (via current_user_can).

To do this check; I do:

register_rest_route(
                        'plugin/v1',
                        '/resource',
                        [
                            [
                                'methods'             => 'GET',
                                'permission_callback' => function ( WP_REST_Request $request ) {
                                    if(! is_user_logged_in()) { return false;}
                                    if(! current_user_can('edit_pages')) { return false;}
                            if(wp_verify_nonce($request->get_header('X-WP-Nonce'),'wp_rest') === false ) { return false; }
                                },
                                'callback'            => 'controller',
                                'args'                => $args
                            ]
                        ]
                    );

is_user_logged_in() is passed, but current_user_can fails, even if I try it with some really basic capability like edit_pages, and run the request as an Administrator user.

Interestingly, if I echo json_encode(wp_get_current_user()) in that permission callback, I can see that the caps property, supposed to represent the user's capabilities, is an empty array. So the admin user has no assigned capabilities, although he's an admin user. Anyone may know why this happens? Because, according to the docs:

Every user logged into WordPress is automatically assigned specific User capabilities depending on their User role.

This does not seem to be the case here??

7
  • How are you authenticating your requests? It's worth a mention that WP_User::$caps is layered on top of any capabilities granted by the user's role - it's not unusual for it to be empty.
    – bosco
    Commented Aug 5, 2022 at 13:47
  • I do both authentication and authorization within the permission_callback. As stated in my question, I'm using is_user_logged_in, hence for authentication, and then current_user_can for authorization. At the moment, it looks like the user is not set because I did not correctly send the nonce, leading to wp nulling the admin user. On the point of figuring it out; will post my answer below.
    – DevelJoe
    Commented Aug 5, 2022 at 13:54
  • 1
    you ask a question about your code without showing it.... for the record you don't need to check if user is logged in, but that is unlikely to be your problem Commented Aug 5, 2022 at 13:56
  • 1
    @bosco I guess my answer posted below answers your concern about authentication.
    – DevelJoe
    Commented Aug 5, 2022 at 14:23
  • 1
    I'm not seeing any code in your question to debug or diagnose
    – Tom J Nowell
    Commented Aug 5, 2022 at 14:27

1 Answer 1

2

Ok I've found the problem. The issue was indeed that I was not passing the nonce of the request in the proper way. Because, as I found in the docs:

If no nonce is provided the API will set the current user to 0, turning the request into an unauthenticated request, even if you’re logged into WordPress.

So I'm guessing that this was the actual issue. So I'm now passing a nonce via the X-WP-Nonce header, with the nonce being used being generated, also as the docs say, via:

wp_create_nonce('wp_rest');

Now, I can indeed retrieve the capabilities of the concerned Administrator user, and current_user_can() authorization within the permission_callback works.

What astonished me is that (and I'm not sure if I'm understanding that correctly); you cannot pass custom strings to wp_create_nonce when you use REST, as you can do it using the legacy wp_ajax API. The above-mentioned failed when I specified any string different from wp_rest to the value of wp_create_nonce. Just feels odd to me that the string used to generate a nonce for like 30 different REST requests MUST be the same string, always.

And finally, after current_user_can, I'm also checking the nonce via:

wp_verify_nonce(
$request->get_header('X-WP-Nonce'),
'wp_rest'
);

Just because the docs say nothing about doing this automatically if you're not using WP's built-in Javascript API. So I guessed you have to do it on your own in such a scenario, but as it seems, WP actually does it for you also in this scenario, so that part can be omitted.

3
  • 1
    you shouldn't need to check for that nonce, the API is doing that for you when authenticating the request
    – Tom J Nowell
    Commented Aug 5, 2022 at 14:28
  • Yeah I thought so too, but the docs are not really clear about that aspect. Although my request failed when I did not generate the nonce with the wp_rest string, so I guess you're right. Meaning you never use wp_verify_nonce in such a scenario?
    – DevelJoe
    Commented Aug 5, 2022 at 14:30
  • 1
    Correct - short of something like using custom nonce actions in the body of the request to verify the intent of more atomic interactions, you will never need to verify a nonce in your REST handlers. Other authentication mechanisms use different means - sometimes just a bearer token with no cookie and no nonce, for example. WordPress handling authentication and request security as a layer separate from routes and endpoints enables them to be written in such a way that your code need not account for the many, many ways in which authentication could occur (unlike the legacy AJAX handler system).
    – bosco
    Commented Aug 5, 2022 at 15:07

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.