Skip to main content

JWTs

Vasyl MartyniukAbout 4 min

The AAM_Framework_Service_Jwts service provides a complete JWT (JSON Web Token) lifecycle management layer for AAM.

It supports:

  • Issuing JWT access tokens
  • Validating token integrity and expiration
  • Revoking tokens
  • Refreshing tokens
  • Maintaining a per-user token registry
  • Querying issued tokens
  • Supporting symmetric and asymmetric signing algorithms

The service is designed specifically for user access levels and integrates tightly with WordPress user meta storage.

Definition

class AAM_Framework_Service_Jwts {

    public get_tokens() : array
    public get_token_by(mixed $search, string $claim = null) : array|WP_Error

    public issue(array $claims = [], array $settings = []) : array
    public validate(string $token) : bool|WP_Error
    public revoke(string $token) : bool
    public refresh(string $token) : array
    public reset() : bool

}

Service Scope

The JWT service can only operate within the context of a user access level.

Internally, the service validates this during initialization:

if ($access_level->type !== AAM_Framework_Type_AccessLevel::USER) {
    throw new LogicException(
        'The JWT service expects ONLY user access level'
    );
}

This means:

  • Tokens are always associated with a WordPress user
  • Token registries are stored per user
  • All issued tokens include the user_id claim

Token Registry

AAM maintains a registry of revocable tokens in WordPress user meta.

Registry storage key is aam_jwt_registry

Registry entries may be stored as either:

[
    'jwt-token-string',
    'another-token'
]

or with metadata:

[
    [
        'token' => 'jwt-token-string',
        'description' => 'API integration token'
    ]
]

The registry is used for:

  • Revocation support
  • Token lookup
  • Validation of revocable tokens
  • Token administration

Issuing Tokens

public function issue(array $claims = [], array $settings = [])

Parameters

$claims

Custom JWT claims to include in the payload.

Example:

[
    'role' => 'api_client',
    'scope' => [
        'read', 
        'write'
    ]
]

$settings

Optional token behavior settings.

Supported settings:

SettingTypeDefaultDescription
ttlstringint+24 hours
revocablebooltrueWhether token can be revoked
refreshableboolfalseWhether token can be refreshed
descriptionstringnullOptional registry label

TTL Handling

TTL can be defined either as:

Relative time string

[
    'ttl' => '+7 days'
]

Numeric seconds

[
    'ttl' => 3600
]

Internally numeric values are normalized into:

+3600 seconds

Invalid TTL values throw:

InvalidArgumentException('Invalid token ttl')

Default Claims

The service automatically injects several claims into every token.

ClaimDescription
jtiUnique token identifier
iatIssued-at timestamp
issWordPress site URL
expExpiration timestamp
user_idWordPress user ID
revocableRevocation support flag
refreshableRefresh support flag

Example payload:

{
  "jti": "5b4f6a2a-1b6e-4bdf-95b8-8393c9f7af12",
  "iat": 1777420000,
  "iss": "https://example.com",
  "exp": 1777506400,
  "user_id": 15,
  "revocable": true,
  "refreshable": false,
  "role": "api_client"
}

Claim Customization Hook

Before token generation, claims pass through:

apply_filters('aam_jwt_claims_filter', $claims)

Example:

add_filter('aam_jwt_claims_filter', function($claims) {
    $claims['environment'] = 'production';

    return $claims;
});

Example: Issue Token

$jwt_service = AAM::api()->jwts();

$result = $jwt_service->issue(
    [
        'scope' => ['read', 'write']
    ],
    [
        'ttl' => '+30 days',
        'refreshable' => true,
        'description' => 'Mobile App Token'
    ]
);

Example response:

[
    'token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
    'claims' => [ ... ],
    'is_valid' => true
]

Validating Tokens

public function validate(string $token)

Return Types

ReturnMeaning
trueToken is valid
WP_ErrorValidation failed

Validation Flow

Validation occurs in two stages.

Stage 1 — Cryptographic Validation

The utility class verifies:

  • JWT structure
  • Supported algorithm
  • Signature integrity
  • nbf claim
  • iat claim
  • exp claim

Stage 2 — Registry Validation

If token claim:

'revocable' => true

then the token must exist in the registry.

Otherwise validation fails with:

RuntimeException('Unregistered token')

Example: Validate Token

$result = $jwt_service->validate($token);

if (is_wp_error($result)) {
    echo $result->get_error_message();
} else {
    echo 'Valid token';
}

Revoking Tokens

public function revoke(string $token)

Behavior

Revocation removes the token from the registry.

Only revocable tokens can be revoked.

Once revoked:

  • Validation fails
  • Refresh fails
  • Registry lookup fails

Example

$jwt_service->revoke($token);

Possible error:

Provided token is not registered

Refreshing Tokens

public function refresh(string $token)

Requirements

The token must:

  • Be valid
  • Not be expired
  • Have:
'refreshable' => true

Refresh Process

When refreshing:

  1. Original token is validated
  2. Claims are decoded
  3. TTL duration is preserved
  4. rat claim is added
  5. New token is issued
  6. Old token is revoked
  7. New token is registered

Refresh Claim

Refreshed tokens receive:

'rat' => time()

This represents:

  • Refresh-at timestamp

Example

$new_token = $jwt_service->refresh($old_token);

Listing Tokens

public function get_tokens()

Returns all tokens from the registry.

Each token is normalized into a consistent structure.

Response Structure

[
    [
        'token' => 'jwt-token',
        'claims' => [ ... ],
        'is_valid' => true,
        'description' => 'Optional label'
    ]
]

If token validation fails:

[
    'token' => 'jwt-token',
    'claims' => [ ... ],
    'is_valid' => false,
    'error' => 'Expired token'
]

Example

$tokens = $jwt_service->get_tokens();

Finding Tokens

public function get_token_by($search, $claim = null)

Lookup by Raw Token

$jwt_service->get_token_by($token);

Lookup by Claim

$jwt_service->get_token_by(15, 'user_id');

Example:

$jwt_service->get_token_by('api_client', 'role');

Return Structure

[
    'token' => 'jwt-token',
    'claims' => [ ... ],
    'is_valid' => true
]

Resetting Tokens

public function reset()

Deletes the entire registry for the current user. This effectively revokes all revocable tokens.

Example

$jwt_service->reset();

Supported Algorithms

The utility supports the following signing algorithms.

AlgorithmType
HS256HMAC SHA256
HS384HMAC SHA384
HS512HMAC SHA512
RS256RSA SHA256
RS384RSA SHA384
RS512RSA SHA512
ES256ECDSA SHA256
ES384ECDSA SHA384
EdDSAlibsodium

Configuration

Signing Algorithm

service.jwt.signing_algorithm

Default:

HS256

HMAC Secret

Used for symmetric algorithms.

service.jwt.signing_secret

Default:

SECURE_AUTH_KEY

Registry Size

Controls maximum stored revocable tokens.

service.jwt.registry_size

Default:

10

When exceeded:

  • Oldest token is removed automatically

Default Expiration

service.jwt.expires_in

Default:

+24 hours

RSA / ECDSA Certificate Configuration

Asymmetric algorithms require certificate paths.

Private Key

service.jwt.private_cert_path

Optional passphrase:

service.jwt.private_cert_passphrase

Public Key

service.jwt.public_cert_path

Path Placeholder

Certificate paths may include:

{ABSPATH}

Example:

{ABSPATH}/certs/private.pem

Security Considerations

Revocable vs Non-Revocable Tokens

Revocable Tokens

Advantages:

  • Can be invalidated
  • Tracked in registry
  • Safer for long-lived access

Disadvantages:

  • Require database lookup
  • Slightly slower validation

Non-Revocable Tokens

Advantages:

  • Stateless validation
  • Faster performance
  • No database dependency

Disadvantages:

  • Cannot be revoked before expiration

Use Short Expiration

Prefer:

'+15 minutes'

instead of:

'+365 days'

Use Refreshable Tokens Carefully

Refreshable tokens are useful for:

  • Mobile applications
  • SPA authentication
  • API sessions

Avoid unlimited refresh loops.

Prefer Revocable Tokens

For administrative access and privileged integrations.

Protect Signing Keys

Never expose:

  • HMAC secret
  • Private RSA/ECDSA certificates

Use HTTPS

JWTs should never travel over unsecured HTTP connections.

Error Handling

Most public methods return either:

  • Valid response
  • WP_Error

Always validate responses.

Example:

$result = $jwt_service->validate($token);

if (is_wp_error($result)) {
    error_log($result->get_error_message());
}

Common Errors

ErrorMeaning
Expired tokenToken expired
Invalid token signatureSignature mismatch
Wrong number of segmentsMalformed JWT
Algorithm not supportedUnsupported JWT algorithm
Unregistered tokenRevoked or unknown token
Cannot take token prior to ...iat or nbf violation
Provided token is not registeredRevoke operation failed
Virtual Assistant