kelvinmo/simplejwt
SimpleJWT is a lightweight PHP library for creating, signing, verifying, and encrypting JSON Web Tokens (JWT/JWS/JWE). Supports JWK/COSE keys, HMAC/RSA/ECDSA/EdDSA algorithms, and common key management and AES encryption methods.
Installation:
composer require kelvinmo/simplejwt
Ensure your composer.json includes PHP 8.0+ and extensions: gmp, hash, openssl, and sodium (for EdDSA/X25519).
First Use Case: Generate a JWT with HMAC-SHA256 (HS256) using a secret:
use SimpleJWT\Keys\KeySet;
$keySet = KeySet::createFromSecret('your-secret-key');
$jwt = new \SimpleJWT\JWT(['alg' => 'HS256'], ['sub' => 'user123']);
$token = $jwt->encode($keySet);
Verify a Token:
$decoded = \SimpleJWT\JWT::decode($token, $keySet, 'HS256');
$claims = $decoded->getClaims();
KeySet for loading keys (PEM, JWK, or secrets).JWT and JWE for signing/verifying and encrypting/decrypting.src/Crypt for supported algorithms (e.g., HS256, RS256, A128GCM).API Authentication:
JWT with asymmetric keys (e.g., RS256) for stateless auth.
$keySet = new KeySet();
$keySet->add(new \SimpleJWT\Keys\RSAKey(file_get_contents('private.pem'), 'pem'));
$jwt = new \SimpleJWT\JWT(['alg' => 'RS256'], ['user_id' => 1]);
$token = $jwt->encode($keySet);
$decoded = \SimpleJWT\JWT::decode($request->bearerToken(), $publicKeySet, 'RS256');
Secure Data Exchange:
JWE for confidential payloads (e.g., A256GCM).
$jwe = new \SimpleJWT\JWE(['alg' => 'PBES2-HS256+A128KW', 'enc' => 'A128GCM'], 'sensitive-data');
$encrypted = $jwe->encrypt($keySet);
$decrypted = \SimpleJWT\JWE::decrypt($encrypted, $recipientKeySet, 'PBES2-HS256+A128KW');
Key Rotation:
KeySet and let the library auto-select the correct one via kid header.kid values.public function handle($request, Closure $next) {
$token = $request->bearerToken();
$keySet = KeySet::createFromSecret(config('jwt.secret'));
$decoded = \SimpleJWT\JWT::decode($token, $keySet, 'HS256');
auth()->setUser($decoded->getClaims()['user_id']);
return $next($request);
}
.env and load them dynamically:
$keySet = new KeySet();
$keySet->add(new \SimpleJWT\Keys\RSAKey(env('JWT_PRIVATE_KEY'), 'pem'));
SimpleJWT\InvalidTokenException for malformed/expired tokens and return 401 responses.Key Format Mismatches:
KeyException.openssl to extract the key:
openssl rsa -in cert.pem -out private.pem
Algorithm Mismatches:
RS256 using HS256 will fail silently (or throw InvalidTokenException).alg header matches the key type:
$alg = $decoded->getHeader('alg');
if ($alg !== 'RS256') throw new \RuntimeException('Invalid algorithm');
Key ID (kid) Handling:
kid headers can cause key selection failures in rotated key sets.kid when adding keys:
$key = new \SimpleJWT\Keys\RSAKey($pemKey, 'pem');
$key->setKeyId('key-1'); // Assign a unique ID
$keySet->add($key);
PHP Extensions:
sodium or gmp extensions will break EdDSA/X25519 algorithms.php.ini or use Docker with pre-configured images.JWT::deserialise() to inspect tokens without verification:
$result = \SimpleJWT\JWT::deserialise($token);
dump($result['claims']); // Check claims before verifying
try {
$key = new \SimpleJWT\Keys\RSAKey($pemKey, 'pem');
$key->validate(); // Throws KeyException if invalid
} catch (\SimpleJWT\KeyException $e) {
// Handle invalid key
}
src/Crypt to avoid unsupported combos (e.g., A128GCM requires sodium).Custom Claims:
Extend JWT to add domain-specific claims:
class CustomJWT extends \SimpleJWT\JWT {
public function __construct(array $headers, array $claims) {
$claims['custom_claim'] = 'value';
parent::__construct($headers, $claims);
}
}
Key Management:
Integrate with Laravel’s Hash or Crypt facades for key storage:
$keySet = new KeySet();
$keySet->add(new \SimpleJWT\Keys\SymmetricKey(
Hash::make('secret'),
'bin'
));
Multi-Tenancy:
Use kid headers to route tokens to tenant-specific key sets:
$kid = $decoded->getHeader('kid');
$tenantKeySet = $this->getKeySetForTenant($kid);
Logging:
Wrap decode() calls to log token validation attempts:
try {
$decoded = \SimpleJWT\JWT::decode($token, $keySet, 'HS256');
} catch (\SimpleJWT\InvalidTokenException $e) {
\Log::warning("Token validation failed: {$e->getMessage()}");
throw $e;
}
iat/kid insertion by passing false to encode():
$jwt->encode($keySet, false); // Skip auto-claims/headers
kid values:
$key->getKeyId(true); // Force RFC7517 thumbnail
base64_encode() calls—use Util::base64url_encode().How can I help you explore Laravel packages today?