
admin
Author
How We Secured WordPress Application Passwords in a Headless Website
This maintenance work started with a WordPress admin failure, but the more important outcome was a safer authorization path for a headless website. The public frontend runs on www.shmlang.com, while WordPress and its REST API live behind api.shmlang.com.
The chosen pattern
We implemented the B + C pattern: the frontend starts the Application Password authorization flow, the www server receives and encrypts the credential, and all privileged WordPress REST calls go through a server-side proxy.
- The user clicks “Connect WordPress” in the settings screen.
- The frontend redirects to the WordPress authorization screen.
- WordPress redirects back to
/api/wp-application/callback. - The server validates the one-time state token.
- The Application Password is encrypted with AES-256-GCM and stored in an HttpOnly cookie.
- Frontend JavaScript never receives or reads the password.
- Future REST calls use
/api/wp-application/proxy/..., where the server attaches Basic Auth.
Why this is safer
An Application Password is a long-lived credential. Storing it in localStorage or returning it to browser JavaScript would make the browser the credential store. In this implementation, the browser only knows whether WordPress is connected; the credential is decrypted and used only on the server.
What was shipped
/api/wp-application/startstarts authorization and creates a state cookie./api/wp-application/callbackvalidates state and stores the encrypted credential./api/wp-application/statusexposes a safe connection status./api/wp-application/disconnectremoves the stored credential./api/wp-application/proxy/[...path]proxies WordPress REST requests from the server.- The dashboard settings screen now has a WordPress API authorization card.
Next step
The current version is a pragmatic single-user credential flow. For multi-device sharing, central revocation, audit logs, and account-level permission management, the encrypted credential should move from a cookie into a database table keyed by user ID.
Comments (0)
No comments yet. Be the first!