Working with claims
Claims are the payload of a token — the statements you’re signing. This guide covers the registered claims the library validates for you, how to add your own, and how to read them back safely.
Setting claims
A JwtPayload is a plain associative array of claims. Standard (registered) claims use
short three-letter keys; you can mix in any custom keys you like:
use Flytachi\Jwt\Entity\JwtPayload;
$now = time();
$payload = new JwtPayload([
// registered claims
'iss' => 'https://my-app.com', // issuer
'sub' => 'user-42', // subject (who the token is about)
'aud' => 'https://api.my-app.com', // audience
'iat' => $now, // issued at
'nbf' => $now, // not valid before
'exp' => $now + 3600, // expires in one hour
// custom claims — anything JSON-serializable
'role' => 'admin',
'scopes' => ['orders:read', 'orders:write'],
]);Time claims are validated automatically
Three registered claims control a token’s validity window, and decode() checks all
three for you:
| Claim | Meaning | Rejected when |
|---|---|---|
exp |
Expiration time | now (minus leeway) is at or past exp |
nbf |
Not before | nbf is later than now (plus leeway) |
iat |
Issued at | iat is later than now (plus leeway) |
Each is optional — omit exp and the token never expires (rarely what you want). All
values are Unix timestamps (seconds).
Always set exp
A token without exp is valid forever, so a leaked one can’t be aged out. Set a short
lifetime (minutes to an hour for access tokens) and issue a fresh token when it lapses.
Allow for clock skew with leeway
Servers’ clocks drift by a few seconds. decode() takes a third argument, leeway
(seconds), that widens the acceptance window on every time check — so a token that
just expired, or whose nbf is a second in your future, isn’t rejected over a tiny
mismatch:
use Flytachi\Jwt\JWT;
// Tolerate up to 60 seconds of clock difference
$payload = JWT::decode($token, [$publicKey], leeway: 60);Keep leeway small — tens of seconds. Large values effectively extend every token’s
life past its stated exp.
Reading claims back
getClaim() reads a single claim, with an optional default when it’s absent:
$userId = $payload->getClaim('sub'); // 'user-42'
$role = $payload->getClaim('role', 'guest'); // default if missing
$scopes = $payload->getClaim('scopes', []); // ['orders:read', ...]
if (in_array('orders:write', $payload->getClaim('scopes', []), true)) {
// authorized to write orders
}Need the whole set at once — for logging or forwarding? Use toArray():
$all = $payload->toArray();
// ['iss' => 'https://my-app.com', 'sub' => 'user-42', 'role' => 'admin', ...]getClaim never throws on a missing claim
An absent claim returns the default (null if you don’t pass one), so reading is safe.
Validate the claims you require explicitly — e.g. reject a token with no sub.
Validate application claims yourself
Beyond the time claims, meaning is up to you. Assert issuer, audience, and any authorization claim after decoding:
use Flytachi\Jwt\JWTException;
$payload = JWT::decode($token, [$publicKey]);
if ($payload->getClaim('aud') !== 'https://api.my-app.com') {
throw new JWTException('Token audience mismatch.');
}
if ($payload->getClaim('role') !== 'admin') {
http_response_code(403);
exit('Admins only.');
}Related
- Verifying with JWKS — checking
iss/audon third-party tokens - HMAC tokens · Asymmetric keys
- API reference —
JwtPayload,decode()leeway