Authenticating the WordPress REST API
The authentication methods available to the WordPress REST API, which suits which integration, and the mistakes that leave endpoints open.

Authentication is where most REST API problems actually live. The data-returning part of an endpoint is usually straightforward; deciding who may call it, and proving they are who they claim, is where sites get opened up by accident.
Here are the options WordPress gives you, what each is for, and the traps in each.
Application passwords
Built into core since WordPress 5.6, and the right default for server-to-server work. A user generates a named password from their profile screen, and it's sent with HTTP basic authentication.
What makes them good is revocability and granularity. Each integration gets its own credential with its own name, so you can see what exists and revoke one without affecting the others or changing the account password. If a credential leaks, you kill that one.
- Create one per integration, named so you know what it is six months later.
- Use a dedicated WordPress account for each integration, with the minimum role it needs — not your administrator account.
- They only work over HTTPS. Over plain HTTP, basic auth hands the credential to anyone on the network.
- Store them in environment variables or a secrets manager, never in a repository.
- Audit and rotate them periodically, and revoke immediately when an integration is decommissioned.
JSON Web Tokens
Common in headless setups where a separate frontend logs users in and then makes requests on their behalf. The frontend exchanges credentials for a signed token, then sends that token as a bearer credential on subsequent requests.
WordPress has no built-in JWT support, so this means a plugin, which means a dependency your authentication depends on. Check its maintenance status carefully before committing.
The implementation details matter more than the concept. Keep token lifetimes short and use refresh tokens rather than long-lived access tokens. Store tokens carefully on the client — localStorage is readable by any script on the page, which makes an XSS hole into a full account compromise. And make sure the plugin validates the signature properly and rejects tokens with the algorithm set to none, which is a classic JWT implementation flaw.
OAuth
OAuth is for third-party applications acting on behalf of your users, where you don't want those users handing over their WordPress password. The user is redirected to your site, approves the application, and the application receives a token scoped to what was approved.
It's the right answer for a platform with many third-party integrations, and considerable over-engineering for a single internal integration. If you're connecting your own CRM to your own site, application passwords do the job with a fraction of the setup.
Choosing between them
- JavaScript on your own WordPress pages → cookies and a nonce.
- A script, a cron job, or another server calling your site → application passwords.
- A headless frontend logging users in and acting as them → JWT, with short-lived tokens.
- Third-party applications acting for your users → OAuth.
- A public read-only endpoint → no authentication, deliberately, with nothing private in the response.
Locking down the whole API
On a site with no public API consumer, the sensible default is to require authentication for everything. The rest_authentication_errors filter lets you reject unauthenticated requests wholesale.
Do it carefully: the block editor and several core features call the API as the logged-in user, so a blanket rejection that doesn't exempt authenticated sessions breaks the admin. The pattern is to reject only requests with no authenticated user, not all requests.
Even on a site that stays public, restrict the users endpoint. By default it publishes author slugs, which is a ready-made username list for a credential-stuffing attempt.
Common mistakes
- permission_callback set to __return_true to silence WordPress's warning, on an endpoint that returns private data.
- Checking that a user is logged in but not what they're allowed to do.
- Credentials in client-side JavaScript, where anyone can read them.
- Basic auth over HTTP rather than HTTPS.
- One shared application password used by four integrations, so revoking it breaks all of them.
- No rate limiting on authentication attempts, turning the API into a brute-force target.
- Verbose error messages that distinguish 'no such user' from 'wrong password', which confirms which accounts exist.
Testing it
The test people skip is the one that matters: call every endpoint with no credentials at all and confirm it refuses. Then call it as a low-privilege user and confirm it still refuses what it should.
Do this for write endpoints specifically, and for any endpoint returning data that isn't public. It takes a few minutes with curl and it catches the class of mistake that causes actual incidents.
Frequently asked questions
What's the easiest way to authenticate against the WordPress REST API?
Application passwords, which are in core since 5.6. Generate one from the user's profile screen, send it with HTTP basic auth over HTTPS, and revoke it independently if it leaks. No plugin needed, and each integration gets its own named credential.
Are application passwords secure?
Yes, provided you use HTTPS — basic auth over plain HTTP hands the credential to anyone on the network. Beyond that, create one per integration on a dedicated account with the minimum role, store them outside your repository, and revoke them when an integration is retired.
Do I need JWT for a headless WordPress site?
Only if your frontend logs users in and acts on their behalf. If it's just reading public content, no authentication is needed at all. If it's a server-side build fetching content, application passwords are simpler and have no plugin dependency.
Why am I getting a 403 'cookie nonce is invalid' error?
The nonce has expired — they last roughly a day, so a page left open overnight will start failing. Refresh the nonce periodically on long-lived pages, or catch the error and prompt a reload. It also happens if a caching layer serves a page with a nonce generated for a different user.
Should I require authentication for the entire REST API?
On a site with no public consumer, yes — use the rest_authentication_errors filter to reject requests with no authenticated user. Reject only unauthenticated requests, not all of them, or you'll break the block editor, which calls the API as the logged-in user.
Topics
- WordPress REST API authentication
- application passwords
- JWT WordPress
- WordPress API security