kelvinmo/simplejwt
SimpleJWT is a lightweight PHP 8+ library for JWT/JWS/JWE and JWK/COSE keys. Supports HS/RSA/ECDSA/EdDSA signatures, key management (RSA-OAEP, AES-KW, PBES2, ECDH-ES/X25519) and AES-GCM/CBC-HS encryption.
Installation:
composer require kelvinmo/simplejwt
Ensure PHP 8.0+ with gmp, hash, openssl, and sodium extensions enabled.
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 the JWT:
$decoded = \SimpleJWT\JWT::decode($token, $keySet, 'HS256');
$claims = $decoded->getClaims();
KeySet and key types (SymmetricKey, RSAKey, etc.) in SimpleJWT/Keys.\SimpleJWT\JWT class methods (encode, decode).$keySet = KeySet::createFromSecret(env('JWT_SECRET'));
$jwt = new \SimpleJWT\JWT(['alg' => 'HS256'], [
'iss' => 'your-app',
'exp' => time() + 3600,
'user_id' => 123
]);
$token = $jwt->encode($keySet);
$keySet = new KeySet();
$privateKey = new \SimpleJWT\Keys\RSAKey(file_get_contents('private.pem'), 'pem');
$keySet->add($privateKey);
$jwt = new \SimpleJWT\JWT(['alg' => 'RS256'], ['sub' => 'user123']);
$token = $jwt->encode($keySet);
$publicKeySet = new KeySet();
$publicKey = new \SimpleJWT\Keys\RSAKey(file_get_contents('public.pem'), 'pem');
$publicKeySet->add($publicKey);
$decoded = \SimpleJWT\JWT::decode($token, $publicKeySet, 'RS256');
KeySet to manage multiple keys (e.g., for rolling keys):
$keySet = new KeySet();
$keySet->load(file_get_contents('keys.json')); // Load JWK Set
$decoded = \SimpleJWT\JWT::decode($token, $keySet, 'RS256');
$keySet = KeySet::createFromSecret('encryption-secret');
$jwe = new \SimpleJWT\JWE(['alg' => 'PBES2-HS256+A128KW', 'enc' => 'A128CBC-HS256'], 'sensitive-data');
$encrypted = $jwe->encrypt($keySet);
$decrypted = \SimpleJWT\JWE::decrypt($encrypted, $keySet, 'PBES2-HS256+A128KW');
$jwt = new \SimpleJWT\JWT([
'alg' => 'HS256',
'custom_header' => 'value'
], [
'sub' => 'user123',
'custom_claim' => 'data'
]);
use SimpleJWT\JWT;
use SimpleJWT\Keys\KeySet;
class VerifyJWTMiddleware
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
$keySet = new KeySet();
$keySet->load(file_get_contents(storage_path('keys/jwks.json')));
try {
$decoded = JWT::decode($token, $keySet, 'RS256');
$request->merge(['user' => $decoded->getClaims()]);
} catch (\SimpleJWT\InvalidTokenException $e) {
abort(401, 'Invalid token');
}
return $next($request);
}
}
Key Management:
KeySet::load() to load JWK Sets from JSON files for key rotation.Algorithm Selection:
Token Storage:
Authorization: Bearer headers.user_id) and fetch data from the database.Performance:
KeySet instances if keys rarely change.Testing:
JWT::deserialise() for unit tests to avoid key dependencies:
$result = JWT::deserialise($token);
$this->assertEquals('user123', $result['claims']['sub']);
Algorithm Mismatch:
alg header matches the key type (e.g., HS256 for symmetric keys, RS256 for RSA).JWT::decode($token, $keySet, 'expected_alg') to enforce validation.Key ID (kid) Issues:
kid is missing or mismatched, tokens may fail verification.kid values:
$key = new \SimpleJWT\Keys\RSAKey($pem, 'pem');
$key->setKeyId('my-key-id'); // Explicitly set kid
Clock Skew:
exp or nbf claims may fail if server clocks are unsynchronized.$decoded = JWT::decode($token, $keySet, 'HS256', 300); // 5-minute leeway
PEM File Parsing:
openssl rsa -in key.pem -outform PEM to extract raw keys.PHP Extensions:
sodium or gmp extensions will break EdDSA/X25519 or RSA operations.Token Tampering:
deserialise() results without verification. Always use decode() in production.Key Rotation:
KeySet to manage multiple keys:
$keySet->add($oldKey);
$keySet->add($newKey);
InvalidTokenException:
SIGNATURE_VERIFICATION_ERROR, TOKEN_EXPIRED).try-catch to log detailed errors:
try {
$decoded = JWT::decode($token, $keySet, 'HS256');
} catch (\SimpleJWT\InvalidTokenException $e) {
\Log::error('JWT Error: ' . $e->getMessage() . ' (Code: ' . $e->getErrorCode() . ')');
}
Algorithm Validation:
How can I help you explore Laravel packages today?