Search by

baxtian / wp_jwt

baxtian

JWT tokens with expiration/revocation for external clients of Merak-based plugins.

Package info

bitbucket.org/baxtian/wp_jwt

pkg:composer/baxtian/wp_jwt

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

0.3.0 2026-09-19 03:46 UTC

This package is auto-updated.

Last update: 2026-09-19 03:50:31 UTC


README

Shared JWT library for Merak-based plugins/themes: short-lived, expirable/revocable tokens for external clients — not a replacement for Application Passwords, which stays the simple option for internal use (admin-to-admin, own tools, no expiration to manage).

Registers its REST routes under jwt-auth/v1 (kept from the jwt-auth plugin this library replaces, for drop-in compatibility with existing clients).

Mantainers

Juan Sebastián Echeverry baxtian.echeverry@gmail.com

Usage Guide

Boot

\Baxtian\WP_Jwt\Auth::get_instance();

Required constants

ConstantRequiredPurpose
JWT_AUTH_SECRET_KEYYesSigns and verifies tokens. Without it, token responds with wp_jwt_bad_config.
JWT_AUTH_CORS_ENABLENoSet true to add Access-Control-Allow-Headers on the normal REST cycle.
API_HOSTNoString or array of allowed origin hosts. When defined, a token request from any other Origin is rejected.

If JWT_AUTH_SECRET_KEY isn't defined, an admin_notices warning is shown automatically once the library boots — no need to build one per project.

Routes

RouteMethodPurpose
jwt-auth/v1/tokenPOSTIssue a token from username/password, or from the refresh_token cookie when present.
jwt-auth/v1/token/validatePOSTValidate the Authorization: Bearer token.
jwt-auth/v1/token/refreshPOSTRotate the refresh_token cookie and issue a new access token.
jwt-auth/v1/loginGETRedeem a one-time login_key (see below) and redirect with a fresh refresh cookie.
jwt-auth/v1/logoutPOSTClear the refresh_token cookie.
jwt-auth/v1/forgot-passwordPOSTSend a password reset email, redirect_url pointing at the consuming app's own reset screen.
jwt-auth/v1/reset-passwordPOSTSet a new password from a reset key.

Extending the credential response

A successful token request fires jwt_auth_valid_credential_response with the built response array and the WP_User, same as the jwt-auth plugin did — this library only adds roles and a one-time login_key; anything project-specific (license state, custom flags) is added by hooking this filter in the consuming project, not in this library.

$response is not always an array. When API_HOST is defined and the request's Origin isn't on the allow-list, this same filter fires with a WP_REST_Response instead (the rejection response itself) — guard with is_array($response) before indexing into it, or the WP_REST_Response case will fatal:

add_filter('jwt_auth_valid_credential_response', function ($response, $user) {
    if (is_array($response) && isset($response['data'])) {
        $response['data']['my_field'] = my_project_value($user);
    }

    return $response;
}, 10, 2);

Outside the REST/hooks cycle (ex. a SHORTINIT script)

validate_token_string(), send_cors_headers(), get_iss(), get_alg() and decode_token() are static — they don't need get_instance(). This matters under SHORTINIT: get_instance() boots the full singleton, which schedules a weekly cron event in its constructor, and cron.php isn't loaded yet at that point (WordPress bails out of SHORTINIT before reaching it in wp-settings.php) — booting the singleton there fatals. get_iss() uses get_option('home') rather than get_bloginfo('url')/home_url() for the same reason (neither is loaded under SHORTINIT either).

validate_token_string() checks the signature and iss, but — unlike the token/validate route — does NOT check that the user still exists: get_user_by()/WP_User need user.php/class-wp-user.php, also not loaded under SHORTINIT. A caller that needs that guarantee checks it itself once its own bootstrap has loaded far enough.

try {
    $payload = \Baxtian\WP_Jwt\Auth::validate_token_string($bearer_token);
} catch (\Exception $e) {
    // invalid/expired token, or iss mismatch
}

\Baxtian\WP_Jwt\Auth::send_cors_headers(['Authorization', 'Content-Type', 'X-Blog-Path'], ['GET', 'OPTIONS']);