Package · jwt

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:

php
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:

php
use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\PublicKey;

$payload = JWT::decode($token, [new PublicKey('my-shared-secret', 'HS256')]);
echo $payload->getClaim('sub'); // user-42

Only 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
php
// 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:

bash
openssl rand -base64 32

Load it from the environment, never from source:

php
$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:

php
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.