firebase/php-jwt
Encode and decode JSON Web Tokens (JWT) in PHP per RFC 7519. Supports common signing algorithms, key handling, header access after verification, and clock-skew leeway. Install via Composer; optional sodium_compat for libsodium environments.
Installation:
composer require firebase/php-jwt
For asymmetric algorithms (e.g., RS256), ensure OpenSSL is enabled in your PHP environment. For libsodium support (e.g., EdDSA), install paragonie/sodium_compat:
composer require paragonie/sodium_compat
First Use Case: Generate a JWT for API authentication:
use Firebase\JWT\JWT;
$key = 'your-secret-key';
$payload = [
'user_id' => 123,
'exp' => time() + 3600, // Expires in 1 hour
];
$jwt = JWT::encode($payload, $key, 'HS256');
Where to Look First:
HS256, RS256, EdDSA).Use JWT in Laravel middleware to validate requests:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
if (!$token) {
return response()->json(['error' => 'Unauthorized'], 401);
}
try {
$decoded = JWT::decode($token, new Key('secret', 'HS256'));
$request->merge(['user' => (array) $decoded]);
return $next($request);
} catch (ExpiredException $e) {
return response()->json(['error' => 'Token expired'], 401);
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid token'], 401);
}
}
Fetch and cache public keys from a JWKS endpoint (e.g., Auth0, Firebase):
use Firebase\JWT\JWK;
use Firebase\JWT\CachedKeySet;
use GuzzleHttp\Client;
use Phpfastcache\CacheManager;
$jwksUri = 'https://your-api.com/.well-known/jwks.json';
$httpClient = new Client();
$cache = CacheManager::getInstance('files');
$keySet = new CachedKeySet(
$jwksUri,
$httpClient,
new \GuzzleHttp\Psr7\HttpFactory(),
$cache,
3600, // Cache for 1 hour
true // Enable rate limiting
);
$decoded = JWT::decode($jwt, $keySet);
Extend payload validation logic:
$payload = [
'iss' => 'your-app.com',
'aud' => 'api.your-app.com',
'scope' => ['read', 'write'],
'exp' => time() + 3600,
];
$jwt = JWT::encode($payload, 'secret', 'HS256');
// Validate on decode
$decoded = JWT::decode($jwt, new Key('secret', 'HS256'));
if (!in_array('read', $decoded->scope)) {
abort(403, 'Insufficient permissions');
}
Use private/public key pairs for server-to-server auth:
// Encode with private key
$privateKey = file_get_contents('private.pem');
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// Decode with public key
$publicKey = file_get_contents('public.pem');
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
Handle encrypted private keys (e.g., RSA with passphrase):
$passphrase = config('jwt.passphrase');
$privateKey = openssl_pkey_get_private(
file_get_contents('private_key.pem'),
$passphrase
);
$jwt = JWT::encode($payload, $privateKey, 'RS256');
Store Keys in Config:
// config/jwt.php
return [
'secret' => env('JWT_SECRET'),
'algorithms' => ['HS256', 'RS256'],
'leeway' => 60,
];
Access via config('jwt.secret').
Service Provider: Bind the JWT decoder as a singleton:
public function register()
{
$this->app->singleton('jwt', function () {
return new \Firebase\JWT\JWT();
});
}
Testing: Mock JWT decoding in tests:
$mockJWT = Mockery::mock('Firebase\JWT\JWT');
$mockJWT->shouldReceive('decode')
->once()
->andReturn((object) ['user_id' => 123]);
$this->app->instance('jwt', $mockJWT);
CachedKeySet to avoid repeated HTTP requests to key endpoints.JWT::$leeway to account for clock skew (e.g., 60 seconds).HS256) for performance if security allows; use asymmetric (RS256) for server-to-server.Algorithm Mismatch:
HS256 with an RSA key or vice versa throws UnexpectedValueException.HS256 → secret key, RS256 → RSA key).Key Length:
HS256 or invalid RSA keys cause DomainException.HS256 or generate valid RSA keys:
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private.pem -out public.pem
Libsodium Dependencies:
EdDSA fails if libsodium is missing, even with paragonie/sodium_compat.sodium_crypto_sign_keypair() is available:
if (!function_exists('sodium_crypto_sign_keypair')) {
throw new \RuntimeException('libsodium extension required for EdDSA');
}
Clock Skew:
JWT::$leeway to a small value (e.g., 60 seconds) to account for skew:
JWT::$leeway = 60;
JWKS Caching:
CachedKeySet may not refresh keys during rotation.$expiresAfter to a low value (e.g., 300 seconds) and enable $rateLimit:
$keySet = new CachedKeySet($jwksUri, $httpClient, $httpFactory, $cache, 300, true);
Passphrase Handling:
openssl_pkey_get_private() to return false.$privateKey = openssl_pkey_get_private($keyFile, $passphrase);
if (!$privateKey) {
throw new \RuntimeException('Invalid passphrase');
}
Custom Headers:
x-forwarded-for) is insecure.list($headerB
How can I help you explore Laravel packages today?