JWTs
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_idclaim
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:
| Setting | Type | Default | Description |
|---|---|---|---|
ttl | string | int | +24 hours |
revocable | bool | true | Whether token can be revoked |
refreshable | bool | false | Whether token can be refreshed |
description | string | null | Optional 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.
| Claim | Description |
|---|---|
jti | Unique token identifier |
iat | Issued-at timestamp |
iss | WordPress site URL |
exp | Expiration timestamp |
user_id | WordPress user ID |
revocable | Revocation support flag |
refreshable | Refresh 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
| Return | Meaning |
|---|---|
true | Token is valid |
WP_Error | Validation failed |
Validation Flow
Validation occurs in two stages.
Stage 1 — Cryptographic Validation
The utility class verifies:
- JWT structure
- Supported algorithm
- Signature integrity
nbfclaimiatclaimexpclaim
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:
- Original token is validated
- Claims are decoded
- TTL duration is preserved
ratclaim is added- New token is issued
- Old token is revoked
- 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.
| Algorithm | Type |
|---|---|
| HS256 | HMAC SHA256 |
| HS384 | HMAC SHA384 |
| HS512 | HMAC SHA512 |
| RS256 | RSA SHA256 |
| RS384 | RSA SHA384 |
| RS512 | RSA SHA512 |
| ES256 | ECDSA SHA256 |
| ES384 | ECDSA SHA384 |
| EdDSA | libsodium |
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
Recommended Practices
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
| Error | Meaning |
|---|---|
Expired token | Token expired |
Invalid token signature | Signature mismatch |
Wrong number of segments | Malformed JWT |
Algorithm not supported | Unsupported JWT algorithm |
Unregistered token | Revoked or unknown token |
Cannot take token prior to ... | iat or nbf violation |
Provided token is not registered | Revoke operation failed |