Skip to content

REST API Authentication: Cookie+Nonce vs. Application Passwords

WordPress’s REST API (covered in an earlier article) mixes two kinds of endpoints: some that anyone can read without authenticating, and others that reject you outright unless you’re logged in. Inside wp-admin, the browser is quietly firing off requests to that same API the whole time you’re editing — and yet you never re-enter your password for each one. This article looks at the mechanism behind that: cookie+nonce authentication, and a separate mechanism built for a different purpose, Application Passwords.

What authenticates a request inside wp-admin

When you log into WordPress, your browser receives an authentication cookie named wordpress_logged_in_*. From then on, every request your browser sends to that same domain carries this cookie automatically, so the server can identify “who’s logged in” just by checking it. When wp-admin’s own JavaScript calls the REST API — auto-saving a draft, or the block editor updating a post in the background — it’s riding on that same cookie.

Note: CSRF (Cross-Site Request Forgery) is an attack where, while you’re logged into a site, a malicious page you happen to open triggers requests to that site using your browser’s cookies without your intent — deleting a post or changing a setting, for example. Because browsers attach cookies automatically, a cookie alone can’t prove the request was something you actually meant to do.

This is exactly the gap CSRF exploits. Since cookies are sent automatically, visiting a booby-trapped page while logged in would carry the same cookie along with it. If the REST API accepted write requests based on cookie validation alone, simply loading a malicious page while logged in could trigger an unwanted post deletion or account change.

Where the nonce comes in

A nonce closes that gap. Despite the name suggesting “used once,” WordPress’s nonce is actually a token tied to the current login session, valid for a fixed window (24 hours by default, extendable to roughly 48 hours with rotation), and scoped to a specific action. The server generates one with wp_create_nonce('some-action-name') and embeds it in the wp-admin page’s HTML/JS. Write requests to the REST API are expected to carry this value in an X-WP-Nonce header, and the server checks it against the logged-in user with wp_verify_nonce().

A malicious external site has no way to obtain that nonce value — it’s only embedded in the wp-admin page itself, which lives on the same origin. So even if an attacker’s page manages to reuse your stolen cookie, the nonce check still blocks it. Put simply: the cookie answers “who is this,” and the nonce answers “did this person actually intend this action from this page, right now.” Both have to check out before a wp-admin REST request goes through.

Where cookie+nonce falls short

This whole design assumes requests originate from the same browser, inside the same wp-admin session. The flip side is that it doesn’t work well for programs that never touch a browser — a command-line tool, or a script running on a different server, that wants to call the REST API directly. Cookies are tied to a browser’s login session, and there’s no natural way for an external program to acquire or maintain one; nonces are similarly short-lived and need to be re-fetched every time a wp-admin page loads.

Application Passwords: a separate door in

Application Passwords, shipped in WordPress core since version 5.6 (2020), answers exactly this need — a way for external programs to call the REST API safely — through a mechanism entirely separate from cookie+nonce. Under the hood it resembles HTTP Basic authentication. From their profile page in wp-admin, a user can generate a new Application Password by naming the application it’s for. What comes out is a randomly generated password distinct from the login password; sent in an Authorization: Basic header, it carries the same permissions as the account.

The key differences from cookie+nonce are twofold. First, it doesn’t depend on any browser session state — once you have the password, any program can use it immediately. Second, each one can be revoked individually. There’s no need to hand your actual login password to an external integration; if you decide to stop using that integration, you revoke just that one Application Password from wp-admin, without touching your login credentials or affecting any other connected tool.

A real case where no authentication is needed at all

Interestingly, the “From the Blog” section on our own LP (en.wpmm.jp/) — built by the blog_latest.php script covered in the earlier article — uses neither of these mechanisms. It only calls /wp/v2/posts, a read-only endpoint for published posts, which is designed to be accessible to anyone without authentication in the first place. REST API authentication only becomes necessary once you go beyond public data — fetching drafts, creating or updating posts, changing settings. Thinking of it in three tiers — anyone can read, only the logged-in user can write, and an external program writes through a dedicated Application Password — makes it straightforward to decide which authentication approach a given task actually needs.

Summary

Requests inside wp-admin combine two checks — a cookie (who’s logged in) and a nonce (did this person intend this action, from this page, right now) — to block CSRF while still letting you skip re-entering your password on every request. But because that design leans heavily on browser session state, it doesn’t extend naturally to external programs calling the REST API. Application Passwords fill that gap with a separate class of token that can be issued and revoked independently of the login password. The same phrase, “REST API authentication,” calls for a different mechanism depending on whether the request originates inside a browser or outside one — worth keeping in mind whenever you’re deciding how a piece of code should authenticate.