Once a route exists you have to decide who may call it. WordPress gives you three practical options, and they differ not in convenience but in where the secret lives.
Cookies and nonces — when a browser calls
This is for a logged-in user’s browser calling the same site’s API. The cookie authenticates; the nonce is not authentication at all but CSRF protection. Conflating the two leads to bad decisions — a nonce is not a secret, and it is perfectly fine printed in the page source.
wp_enqueue_script( 'wper-app', $url, [], $ver, true );
wp_localize_script(
'wper-app',
'wperApi',
[
'root' => esc_url_raw( rest_url( 'wper/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
]
);
// Client: fetch( wperApi.root + 'orders/12', {
// credentials: 'same-origin',
// headers: { 'X-WP-Nonce': wperApi.nonce }
// } )
The trap here is that nonces expire. Press save in a tab left open overnight and you get a 403 with rest_cookie_invalid_nonce. To the user that reads as “it worked a moment ago”, so the client has to catch that error and prompt a reload, or refresh the nonce through the heartbeat.
The threat model is clear: the secret is the browser session, so a successful XSS is the end of the story. Output escaping matters most precisely on the screens that use this method.
Application passwords — when a server calls
Another server or a script has no cookie to send. Core application passwords exist for this. You issue one per application from the user profile, send it over HTTP Basic, and revoke it individually without touching anything else.
curl -u 'ci-bot:abcd EFGH ijkl MNOP qrst UVWX'
https://example.com/wp-json/wp/v2/posts?status=draft
Three properties matter. First, it cannot exceed the user’s own capabilities — so create a dedicated automation user with the smallest role that works. Second, it is separate from the account password, so a leak costs you one credential rather than the account. Third, HTTPS is a precondition: Basic auth puts the credential straight into a header.
The threat model is “a long-lived credential sitting in a config file”. Keeping it out of the repository and deciding the rotation procedure in advance is the homework this method sets.
Tokens — when a third party issues them
Tokens belong where a mobile app or a separate front end calls on behalf of its own users, or where you genuinely need short-lived credentials. This is not core, so a plugin or your own implementation fills the gap — and at that moment you begin operating an issuer and a refresh flow.
Worth being honest here: tokens are not safer because they are more modern. Short expiry narrows the window of a leak, but it buys you refresh-token storage, revocation lists and key rotation — new operational load. If one server posts articles on a nightly batch, an application password is the right answer and a token is overkill.
The wider view on hardening lives in the Security archive, and if you would like authentication and permissions reviewed alongside everything else, a diagnostic is part of our optimization program.
Next part
The fourth line of that list is the next part. A webhook from an external service carries no cookie and no nonce, so a nonce check fails one hundred per cent of the time. We will look at what to verify instead.