WordPress22/09/2026Redação de Tecnologia e Desenvolvimento7 min readUpdated on 25/09/2026

WordPress REST API: nonce authenticates, permission_callback authorizes, and current_user_can protects the route

Learn how authentication and authorization differ in the WordPress REST API, with nonce, permission_callback, current_user_can, and regression tests.

Mesa de desenvolvimento com um fluxo visual da REST API do WordPress, mostrando uma etapa de nonce e outra de controle de permissão antes do endpoint.

Short answer: in the WordPress REST API, authentication and authorization are separate stages. When a request uses WordPress core cookie authentication, the wp_rest action nonce helps core validate request intent and associate the call with the authenticated session. It does not grant permission to read, create, update, or delete resources. The access decision belongs in permission_callback, usually with current_user_can() and a capability that matches the resource.

This separation prevents a common mistake: assuming that a request with a valid X-WP-Nonce is already authorized. A user can be authenticated and still lack the required capability. WordPress documentation recommends checking what the user can do rather than only checking whether the user is logged in.

Authentication and authorization answer different questions

LayerQuestionTypical mechanism
AuthenticationWho is making the request?WordPress cookies with a wp_rest nonce, Application Passwords, or another authentication method
AuthorizationMay this user perform this action?permission_callback with current_user_can() or another access rule
ExecutionWhat happens after access is approved?The route callback, with validation, sanitization, and business logic

The secure flow is straightforward: WordPress first resolves the available authentication; then, before the main callback, it runs permission_callback. Only after permission is granted should the route logic continue.

What the REST nonce actually does

With cookie authentication, used mainly when JavaScript runs inside a WordPress site where the user is already logged in, the client sends a nonce created for the wp_rest action. The value can be sent in the X-WP-Nonce header or the _wpnonce parameter. The documentation recommends the header because it behaves consistently across HTTP methods.

If a request relies on cookies but does not provide a valid REST nonce, core should not treat that session as authenticated for the call. The REST API Handbook explains that without the nonce the current user is set to ID 0, so the call behaves as unauthenticated. When the nonce is invalid, the cookie authentication mechanism can stop the request with an error.

For custom endpoints, you do not need to manually verify the wp_rest nonce again inside the endpoint callback. Core performs that check during cookie authentication through rest_cookie_check_errors(). The important point is that this does not replace authorization. The WordPress nonces documentation explicitly says that nonces should not be used for access control and recommends protecting operations with current_user_can().

There is another important nuance: WordPress nonces are not one-time tokens in the strict cryptographic sense. They have a validity window and can be reused during that window. They should not be treated as a password-equivalent secret or as sufficient proof of permission.

Registering a private route with permission_callback

Consider a route that changes a plugin setting. Because the operation is administrative, the example below requires the manage_options capability. In a real plugin, choose the most specific capability that matches the action.

<?php add_action( 'rest_api_init', function () { register_rest_route( 'acme/v1', '/settings', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => 'acme_update_settings', 'permission_callback' => 'acme_update_settings_permissions_check', 'args' => array( 'value' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); } ); function acme_update_settings_permissions_check( WP_REST_Request $request ) { if ( ! current_user_can( 'manage_options' ) ) { return new WP_Error( 'acme_rest_forbidden', __( 'You are not allowed to change this setting.', 'acme' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } function acme_update_settings( WP_REST_Request $request ) { $value = $request->get_param( 'value' ); update_option( 'acme_example_value', $value ); return rest_ensure_response( array( 'success' => true, 'value' => $value, ) ); }

permission_callback may return true, false, or a WP_Error. Returning WP_Error is useful when you want a specific code and message. If it returns false or null, the REST server creates an access-denied response. The rest_authorization_required_code() helper returns 401 for an unauthenticated visitor and 403 for an authenticated user who is not allowed to perform the action.

Since WordPress 5.5, registering a route without permission_callback triggers an incorrect-usage notice. For genuinely public routes, __return_true can be appropriate. On a private route, using __return_true only to silence the notice removes the authorization barrier itself.

Three scenarios that show why a nonce is not enough

ScenarioAuthenticationAuthorizationExpected result
Anonymous visitorNo authenticated sessioncurrent_user_can() failsAccess denied, normally 401
Logged-in user with a valid nonce but without the capabilityThe session can be recognizedcurrent_user_can() failsAccess denied, normally 403
Logged-in user with a valid nonce and the required capabilityThe session is recognizedcurrent_user_can() passesThe route callback runs

The second scenario is the most important mental test. A valid nonce can accompany a request from a subscriber, author, or another user who lacks the required permission. If the route only checks the nonce or only calls is_user_logged_in(), it may expose an operation that should remain restricted.

When to use a nonce and when to use another authentication method

The wp_rest nonce fits the cookie-authentication flow, especially on admin screens, blocks, plugins, or front ends rendered by WordPress itself, where the browser already holds the session cookies. In that case, JavaScript sends the nonce with the REST call.

For external integrations, services on another server, automation scripts, or clients that do not share the browser cookie session, WordPress documentation presents Application Passwords as the native method for REST authentication over HTTPS. Even with Application Passwords, route authorization remains the responsibility of permission_callback.

In other words, the authentication method may change, but the authorization rule remains. The endpoint must decide whether the authenticated identity can perform the requested operation.

Prefer capabilities over role names

current_user_can() should receive a capability that represents the action. Directly checking whether the user is an administrator, editor, or another role creates unnecessary coupling and can fail on sites that customize roles and capabilities.

When authorization depends on a specific object, prefer a meta capability with the matching object ID. To edit a specific post, for example:

function acme_update_post_permissions_check( WP_REST_Request $request ) { $post_id = (int) $request['id']; return current_user_can( 'edit_post', $post_id ); }

WordPress maps meta capabilities such as edit_post to the required primitive capabilities according to the user, object, and context. This is more precise than a generic check such as edit_posts when the decision depends on one particular item.

Common mistakes when protecting a REST route

  • Using a nonce as authorization: it helps protect the request flow, but it does not say whether the user may perform the action.
  • Checking only whether the user is logged in: authentication without a capability can grant excessive access to low-privilege accounts.
  • Putting all authorization inside the main callback: the check should happen in permission_callback, before business logic.
  • Using __return_true on a private endpoint: this authorizes the route publicly even if the callback later tries to compensate.
  • Checking a role instead of a capability: capabilities are the WordPress abstraction intended for authorization.
  • Manually rechecking the REST nonce without a reason: in the standard cookie flow, core already verifies wp_rest. Duplicate the check only if you have a separate, well-defined security requirement.
  • Trusting client parameters to decide privilege: IDs and arguments can inform the check, but the server must make the decision using trusted data and capabilities.

Regression test: anonymous, user without capability, and authorized user

In addition to testing the successful case, keep tests that prove the route still denies access when authentication or capability changes. A unit or integration test can dispatch a WP_REST_Request directly to the REST server to validate the authorization rule.

<?php public function test_settings_route_permissions() { $server = rest_get_server(); wp_set_current_user( 0 ); $request = new WP_REST_Request( 'POST', '/acme/v1/settings' ); $request->set_param( 'value', 'one' ); $response = $server->dispatch( $request ); $this->assertSame( 401, $response->get_status() ); $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); wp_set_current_user( $subscriber_id ); $request = new WP_REST_Request( 'POST', '/acme/v1/settings' ); $request->set_param( 'value', 'two' ); $response = $server->dispatch( $request ); $this->assertSame( 403, $response->get_status() ); $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); $request = new WP_REST_Request( 'POST', '/acme/v1/settings' ); $request->set_param( 'value', 'three' ); $response = $server->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); }

This test covers endpoint authorization. If the plugin depends on JavaScript authenticated with cookies, also add an HTTP integration test for the nonce flow: logged-in session without X-WP-Nonce, invalid nonce, valid nonce with a user lacking the capability, and valid nonce with an authorized user. This separates authentication regressions from authorization regressions.

Checklist for a private REST route

  • Register the route on rest_api_init.
  • Define permission_callback explicitly.
  • Choose a capability tied to the real action, using a meta capability and object ID when needed.
  • Use a wp_rest nonce only when the call depends on cookie authentication.
  • For external clients, use an appropriate authentication method such as Application Passwords over HTTPS.
  • Validate and sanitize request arguments separately from authorization.
  • Keep the main callback focused on operation logic after permission has been approved.
  • Test at least anonymous, authenticated without the capability, and authenticated with the capability.

Official sources

Topics in this articlecurrent_user_canendpointsnoncepermission_callbackREST APIsegurançaWordPress