The WordPress REST API is a powerful tool for developers to interact programmatically with WordPress sites. While it supports several built-in authentication methods (e.g., cookies, OAuth, and application passwords), there are scenarios where a custom solution like JSON Web Tokens (JWT) is preferable. WordPress REST API JWT Authentication offer stateless authentication, scalability, and compatibility with modern decoupled architectures. However, implementing a custom authentication method requires careful planning to avoid conflicts with existing authentication flows.
This guide provides an orderly process for integrating JWT token authentication into the WordPress REST API while preserving compatibility with core authentication systems.
JWT tokens are compact, URL-safe tokens that securely transmit information between parties. They are ideal for REST API authentication because:
Key Challenges:

To handle JWT encoding/decoding, use a trusted library like firebase/php-jwt. Install it via Composer:
composer require firebase/php-jwt
If Composer isn’t available, download the library manually and include it in your plugin:
// Include the JWT library
require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
Create a custom REST API endpoint where users can exchange their credentials for a JWT token.
Add the following to your plugin or theme’s functions.php:
add_action('rest_api_init', 'register_jwt_auth_endpoints');
function register_jwt_auth_endpoints() {
register_rest_route('jwt-auth/v1', '/token', array(
'methods' => 'POST',
'callback' => 'issue_jwt_token',
));
}
Validate user credentials and issue a token:
function issue_jwt_token(WP_REST_Request $request) {
$username = $request->get_param('username');
$password = $request->get_param('password');
// Authenticate the user
$user = wp_authenticate($username, $password);
if (is_wp_error($user)) {
return new WP_REST_Response(
array('error' => 'Invalid credentials'),
401
);
}
// Generate JWT payload
$issued_at = time();
$expiration = $issued_at + (DAY_IN_SECONDS * 1); // 1-day lifespan
$payload = array(
'iss' => get_bloginfo('url'),
'iat' => $issued_at,
'exp' => $expiration,
'nbf' => $issued_at,
'data' => array(
'user_id' => $user->ID,
)
);
// Sign the token with a secret key
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : 'your-strong-secret-here';
$token = JWT::encode($payload, $secret_key, 'HS256');
return new WP_REST_Response(array(
'token' => $token,
'user_id' => $user->ID,
'expires_in' => $expiration
), 200);
}
Explanation:
Intercept incoming API requests, validate the JWT token, and authenticate the user.
add_filter('rest_pre_dispatch', 'validate_jwt_token', 10, 3);
function validate_jwt_token($result, $server, $request) {
// Skip validation if the token is absent
$auth_header = $request->get_header('Authorization');
if (!$auth_header || !preg_match('/Bearer\s(\S+)/', $auth_header, $matches)) {
return $result;
}
$token = $matches[1];
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : 'your-strong-secret-here';
try {
// Decode and validate the token
$payload = JWT::decode($token, new Key($secret_key, 'HS256'));
$user_id = $payload->data->user_id;
// Set the current user
wp_set_current_user($user_id);
} catch (Exception $e) {
return new WP_Error(
'jwt_auth_invalid_token',
'Invalid or expired token',
array('status' => 401)
);
}
return $result;
}
Key Points:
To prevent conflicts with WordPress default authentication systems:
// php Code
// In wp-config.php
define('JWT_AUTH_SECRET_KEY', 'your-random-256-bit-secret');
Extend the token system to allow renewal without re-authentication.
// php code
register_rest_route('jwt-auth/v1', '/refresh', array(
'methods' => 'POST',
'callback' => 'refresh_jwt_token',
));
function refresh_jwt_token(WP_REST_Request $request) {
$old_token = $request->get_param('refresh_token');
try {
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : 'your-strong-secret-here';
$payload = JWT::decode($old_token, new Key($secret_key, 'HS256'));
// Validate the refresh token (e.g., check a allowlist)
if ($payload->type !== 'refresh') {
throw new Exception('Invalid token type');
}
// Issue a new access token
$user_id = $payload->data->user_id;
$user = get_user_by('ID', $user_id);
$issued_at = time();
$expiration = $issued_at + (DAY_IN_SECONDS * 1);
$payload = array(
'iss' => get_bloginfo('url'),
'iat' => $issued_at,
'exp' => $expiration,
'data' => array('user_id' => $user_id)
);
$new_token = JWT::encode($payload, $secret_key, 'HS256');
return new WP_REST_Response(array(
'token' => $new_token,
'expires_in' => $expiration
), 200);
} catch (Exception $e) {
return new WP_REST_Response(array('error' => 'Invalid refresh token'), 401);
}
}
Use Postman or Curl to test the endpoints:
// bash code
curl -X POST https://yoursite.com/wp-json/jwt-auth/v1/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your_password"}'
// bash code
curl https://yoursite.com/wp-json/wp/v2/users/me \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
// bash code
curl -X POST https://yoursite.com/wp-json/jwt-auth/v1/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"YOUR_REFRESH_TOKEN"}'
Implementing a custom JWT authentication method for the WordPress REST API enhances security and flexibility, especially for modern applications that require stateless authentication. By following the steps outlined in this guide, developers can establish a powerful authentication system that functions alongside existing methods, ensuring a seamless user experience.
Remember to adhere to security best practices, regularly audit your implementation, and keep your JWT library updated to mitigate vulnerabilities. With JWT tokens, you can empower your WordPress applications to interact securely and efficiently with various clients, paving the way for innovative solutions in the WordPress ecosystem.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.