HMAC tokens
HMAC is the simplest way to use JWTs: one secret string signs and verifies. Reach for it when the same service both issues and checks tokens. New here? Start with the Quickstart.
Sign with a secret
encode() takes a JwtPayload and a PrivateKey. For HMAC the key material is just
your secret string:
use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\JwtPayload;
use Flytachi\Jwt\Entity\PrivateKey;
$token = JWT::encode(
new JwtPayload([
'sub' => 'user-42',
'iat' => time(),
'exp' => time() + 3600,
]),
new PrivateKey('my-shared-secret', 'HS256')
);Verify with the same secret
decode() takes the token and a list of PublicKeys. For HMAC you pass exactly one —
the verifier uses the first key in the array and ignores any kid:
use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\PublicKey;
$payload = JWT::decode($token, [new PublicKey('my-shared-secret', 'HS256')]);
echo $payload->getClaim('sub'); // user-42Only one key for HMAC
decode() selects the first PublicKey in the list for any HS* token — it never
looks at kid. Pass just the one secret. Multiple-key selection is an asymmetric-only
feature; see Verifying with JWKS.
Pick a hash strength
The three HMAC algorithms differ only in the underlying SHA hash. The signer and verifier must agree — the algorithm is pinned to the key on both sides.
| Algorithm | Hash | Signature size |
|---|---|---|
HS256 |
SHA-256 | 32 bytes |
HS384 |
SHA-384 | 48 bytes |
HS512 |
SHA-512 | 64 bytes |
// Stronger hash — everything else is identical
$token = JWT::encode(
new JwtPayload(['sub' => 'user-42']),
new PrivateKey('my-shared-secret', 'HS512')
);
$payload = JWT::decode($token, [new PublicKey('my-shared-secret', 'HS512')]);HS256 is the standard choice and plenty strong when your secret is long and random.
HS384/HS512 add margin at a marginal CPU cost.
Generate a strong secret
An HMAC secret should be high-entropy — treat it like a password, not a passphrase:
openssl rand -base64 32Load it from the environment, never from source:
$secret = getenv('JWT_SECRET')
?: throw new RuntimeException('JWT_SECRET is not set');
$token = JWT::encode(
new JwtPayload(['sub' => 'user-42', 'exp' => time() + 3600]),
new PrivateKey($secret, 'HS256')
);Handle the failure paths
Every rejection — wrong secret, tampered token, expired — arrives as a single
JWTException:
use Flytachi\Jwt\JWTException;
try {
$payload = JWT::decode($token, [new PublicKey($secret, 'HS256')]);
} catch (JWTException $e) {
// "Signature verification failed." / "Token has expired (exp)." / ...
http_response_code(401);
exit($e->getMessage());
}One secret, two powers
Because HMAC is symmetric, anyone with the secret can mint tokens, not just verify them. If you need third parties to verify without being able to forge, use asymmetric keys instead.
Related
- Asymmetric keys — RSA & ECDSA when verifiers shouldn’t sign
- Working with claims — expiry,
nbf, leeway, custom data - API reference —
encode(),decode(), key objects