JWT Authentication with the WordPress REST API in TypeScript
JWT Authentication with the WordPress REST API in TypeScript
If you have ever tried to integrate a TypeScript app — whether a Next.js frontend, a Node.js backend service, or a React Native mobile app — with a WordPress backend, you have likely hit the same wall: WordPress ships with Basic Auth and cookie-based auth baked in, but neither is suitable for decoupled or headless architectures.
Basic Auth sends credentials on every request. Cookie-based auth is tied to the browser session model. Neither works cleanly when your TypeScript app lives on a separate origin, runs server-side, or needs to hand off a token to a mobile client. JWT (JSON Web Token) solves all of this: authenticate once, receive a signed token, attach it to subsequent requests, and revoke it on logout.
This tutorial walks you through the full flow using @atomic-solutions/wordpress-utils — a platform-agnostic TypeScript client for the WordPress REST API with Zod-validated responses and 112+ tests.
Prerequisites: The JWT Auth plugin on the WordPress side
@atomic-solutions/wordpress-utils handles the client side. On the server side, your WordPress installation needs to expose a JWT endpoint. The standard choice is the JWT Authentication for WP REST API plugin.
Install it, then add the following to your wp-config.php:
define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');
define('JWT_AUTH_CORS_ENABLE', true);
Also add the Authorization header rewrite rule to your .htaccess (Apache) or Nginx config so the header reaches PHP:
# Apache
RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
Once the plugin is active and configured, WordPress exposes POST /wp-json/jwt-auth/v1/token for login and POST /wp-json/jwt-auth/v1/token/validate for token validation. The client library handles these endpoints transparently.
Installation
pnpm add @atomic-solutions/wordpress-utils
The package ships TypeScript types and has no runtime peer dependencies beyond zod.
Making an unauthenticated request
Most WordPress REST API endpoints are public. You do not need to authenticate to list published posts or retrieve a single post by ID. Start here to verify connectivity:
import { createClient } from '@atomic-solutions/wordpress-utils'
const client = createClient({
baseURL: 'https://your-site.com/wp-json',
})
const { data: posts } = await client.posts.list({ per_page: 5 })
for (const post of posts) {
console.log(post.id, post.title.rendered)
}
createClient returns a typed client. All responses are validated with Zod at runtime, so if the WordPress API returns an unexpected shape — say, because a plugin added a non-standard field in a breaking way — you will get a clear validation error rather than a silent type mismatch at runtime.
client.posts.list() accepts the full set of WordPress REST API query parameters: per_page, page, search, categories, tags, order, orderby, and more — all typed.
Logging in and making an authenticated request
Protected endpoints — creating or editing posts, accessing user data, managing orders in WooCommerce — require authentication. Call client.auth.login() with the WordPress credentials, then proceed with protected calls:
const client = createClient({
baseURL: 'https://your-site.com/wp-json',
})
// Login — stores the JWT token internally for subsequent requests
await client.auth.login({ username: 'editor', password: 'secret' })
// Now authenticated — can access protected endpoints
const me = await client.users.me()
console.log(`Logged in as ${me.name}`)
After a successful login, the client stores the token in memory and attaches it as a Bearer token on every subsequent request. You do not need to manage the Authorization header yourself.
client.users.me() returns the currently authenticated user’s profile — a useful way to verify the token is working before proceeding to write operations.
Storing the token across sessions
The in-memory token is convenient but ephemeral: it disappears when your process restarts or the user navigates away. For a Next.js app you might store the token in a server-side session or an httpOnly cookie. For a React Native app, you would persist it with expo-secure-store or the equivalent.
The client accepts a pre-existing token at construction time, so restoring a session is straightforward:
import { createClient } from '@atomic-solutions/wordpress-utils'
// Retrieve previously stored token (implementation depends on your platform)
const storedToken = await getTokenFromSecureStorage()
const client = createClient({
baseURL: 'https://your-site.com/wp-json',
auth: storedToken
? { token: storedToken }
: undefined,
})
// If we have a stored token, users.me() works immediately
if (storedToken) {
const me = await client.users.me()
console.log(`Restored session for ${me.name}`)
}
When you receive the token from a fresh auth.login() call, read it back from the client and persist it using your preferred storage mechanism before the session ends.
Logging out
Logout revokes the token on the WordPress side and clears the in-memory state on the client:
await client.auth.logout()
// Subsequent authenticated requests will now return 401
For user-facing apps, call logout on an explicit “Sign out” action or when a 401 response indicates the token has expired.
Error handling
Authenticated endpoints return 401 Unauthorized when the token is missing, expired, or invalid. Wrap calls in try/catch and respond appropriately:
import { createClient, WordPressAuthError } from '@atomic-solutions/wordpress-utils'
const client = createClient({ baseURL: 'https://your-site.com/wp-json' })
try {
await client.auth.login({ username: 'editor', password: 'wrong-password' })
} catch (err) {
if (err instanceof WordPressAuthError) {
console.error('Authentication failed:', err.message)
// Redirect to login page, clear stored tokens, etc.
} else {
throw err
}
}
The client surfaces WordPress API errors as typed exceptions, so you can distinguish authentication failures from network errors or validation failures without parsing raw response bodies.
For a full reference of available methods and configuration options, see the wordpress-utils API client documentation.
Conclusion
JWT authentication against the WordPress REST API from TypeScript does not need to be painful. The flow is:
- Install the JWT Auth plugin on the WordPress server.
- Install
@atomic-solutions/wordpress-utilsin your TypeScript project. - Call
createClient({ baseURL }), thenauth.login()once. - Make authenticated requests — the token is attached automatically.
- Persist the token for session restoration; call
auth.logout()to revoke it.
The library handles Zod validation, typed errors, and the Authorization header for you, so you can focus on building your product rather than wrestling with HTTP plumbing.
Need help shipping a headless WordPress project in production? Atomic Solutions builds headless WordPress storefronts for clients.