How to configure JWT authentication for the WordPress REST API
To configure JWT authentication for the WordPress REST API you need to install a JWT plugin, add a secret key to wp-config.php, adjust your server’s rewrite rules, generate a token, and then include that token in the Authorization header of every request your head‑less React app makes.
Prerequisites
- WordPress 6.4 or later, running on Apache or Nginx.
- Access to the site’s file system and the ability to edit
.htaccess(Apache) or the server block (Nginx). - PHP 7.4 or later.
- A React project that will call the WordPress REST endpoints.
Step 1 – Install the JWT Authentication plugin
- From the WordPress admin dashboard go to Plugins → Add New.
- Search for “JWT Authentication for WP‑REST API”. The current stable version is 2.5.0.
- Click Install Now and then Activate.
The plugin registers two new routes:
/wp-json/jwt-auth/v1/token– POST request to obtain a token./wp-json/jwt-auth/v1/token/validate– GET request to validate an existing token.
Step 2 – Add a secret key to wp-config.php
Edit the root wp-config.php file and add a unique phrase. It must be a long, random string; you can generate one with openssl rand -base64 32 on the command line.
// wp-config.php
define('JWT_AUTH_SECRET_KEY', 'your‑random‑base64‑string‑here');
define('JWT_AUTH_CORS_ENABLE', true); // optional, enables CORS for the token route
Do not expose this key in version control. Keep it private on the server.
Step 3 – Configure the web server
Apache
If you are using Apache, the plugin requires the Authorization header to be passed to PHP. Add the following lines to the site’s .htaccess file, directly above the WordPress rewrite block.
# .htaccess – pass Authorization header to PHP
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*)$ - [E=HTTP_AUTHORIZATION:%1]
Nginx
For Nginx, add the header forwarding directive inside the location / block.
# nginx.conf – forward Authorization header
location / {
try_files $uri $uri/ /index.php?$args;
proxy_set_header Authorization $http_authorization;
}
Step 4 – Test token generation
Use curl or a tool like Postman to request a token. Replace example.com with your domain and admin/password with a valid user’s credentials.
curl -X POST http://example.com/wp-json/jwt-auth/v1/token \
-d "username=admin" \
-d "password=password"
A successful response looks like:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user_email": "admin@example.com",
"user_nicename": "admin",
"user_display_name": "admin"
}
Copy the token value; it will be sent with every API request.
Step 5 – Use the token in a React fetch call
Below is a minimal example using the native fetch API. The token is stored in localStorage after the login request.
// src/api.js – React helper
export async function login(username, password) {
const response = await fetch('https://example.com/wp-json/jwt-auth/v1/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('jwt', data.token);
return data;
}
throw new Error(data.message || 'Login failed');
}
export async function fetchProtected(endpoint) {
const token = localStorage.getItem('jwt');
const response = await fetch(`https://example.com/wp-json${endpoint}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// token invalid or expired – handle re‑login
throw new Error('Unauthorised – token may have expired');
}
return response.json();
}
Call fetchProtected('/wp/v2/posts') to retrieve posts that require authentication.
Step 6 – Handling token expiry
The JWT plugin does not provide a refresh endpoint out of the box. The usual approach is to store the token’s expiry timestamp (available in the token payload) and, when the current time exceeds it, force the user to log in again. You can decode the payload client‑side without verification:
function getTokenExpiry(token) {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp; // Unix timestamp
}
If Date.now() / 1000 > getTokenExpiry(token) you should clear localStorage and redirect to the login page.
Step 7 – Common pitfalls and how to avoid them
- Missing Authorization header on Apache. Without the rewrite rule in
.htaccessthe header never reaches PHP, resulting in “Invalid token” errors. - CORS errors. The plugin disables CORS by default. Setting
JWT_AUTH_CORS_ENABLEtotrueinwp-config.phpallows cross‑origin requests from your React app. If you need tighter control, configure theAccess-Control-Allow-Originheader in your server block. - Using a non‑HTTPS endpoint. JWT tokens are signed but not encrypted; transmitting them over HTTP exposes them to sniffing. Always serve the token endpoint and the REST API over HTTPS.
- Incorrect user role. By default the plugin allows any user with the
readcapability to obtain a token. If you want to restrict token issuance to administrators, add a filter infunctions.php:
// functions.php – restrict token generation
add_filter('jwt_auth_token_before_dispatch', function($data, $user) {
if (!user_can($user, 'manage_options')) {
return new WP_Error('jwt_auth_invalid_user', 'User does not have permission', array('status' => 403));
}
return $data;
}, 10, 2);
Step 8 – Securing the API further
If you need fine‑grained control, combine JWT authentication with WordPress capabilities. In your custom REST route callback you can check the token’s user ID and verify capabilities with current_user_can(). This ensures that even a valid token cannot access resources the user is not authorised for.
Testing the full flow
- Run the login function in your React app and confirm a token is stored.
- Open the browser’s developer tools, go to the Network tab, and verify that the
Authorization: Bearer …header is present on a request to/wp/v2/posts. - Inspect the response; you should see a JSON array of posts. If you receive a 401, check the server logs for “JWT token not found” or “Invalid token”.
- Force token expiry by deleting the token from
localStorageand repeat step 2 to ensure your error handling works.
Conclusion
By installing the JWT Authentication plugin, adding a secret key, configuring the web server to forward the Authorization header, and attaching the token to each fetch request, you can securely expose WordPress data to a headless React front‑end without exposing the admin UI.