firebase/php-jwt
Encode and decode JSON Web Tokens (JWT) in PHP per RFC 7519. Supports common signing algorithms, key handling, and optional leeway for clock skew. Install via Composer; libsodium compatible via sodium_compat.
Installation:
composer require firebase/php-jwt
For environments without libsodium, add:
composer require paragonie/sodium_compat
First Use Case:
Encode a JWT with a symmetric key (e.g., HS256):
use Firebase\JWT\JWT;
$key = 'your-secret-key';
$payload = ['user_id' => 123, 'iat' => time()];
$jwt = JWT::encode($payload, $key, 'HS256');
Decode it in a Laravel middleware or controller:
$decoded = JWT::decode($jwt, new \Firebase\JWT\Key($key, 'HS256'));
$userId = $decoded->user_id;
Key Locations:
.env (e.g., JWT_SECRET=your-key).config() helper to centralize keys:
config(['jwt.secret' => env('JWT_SECRET')]);
// In a Laravel controller or service
$token = JWT::encode([
'user_id' => auth()->id(),
'roles' => ['admin'],
'exp' => now()->addHours(1)->timestamp,
], config('jwt.secret'), 'HS256');
return response()->json(['token' => $token]);
namespace App\Http\Middleware;
use Closure;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class AuthenticateJWT
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
if (!$token) return response()->json(['error' => 'Unauthorized'], 401);
try {
$decoded = JWT::decode($token, new Key(config('jwt.secret'), 'HS256'));
$request->merge(['user' => (array) $decoded]);
return $next($request);
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid token'], 401);
}
}
}
Register in app/Http/Kernel.php:
protected $routeMiddleware = [
'jwt.auth' => \App\Http\Middleware\AuthenticateJWT::class,
];
storage/app/jwt/ or use Laravel’s filesystem:
$privateKey = file_get_contents(storage_path('app/jwt/private.pem'));
$publicKey = file_get_contents(storage_path('app/jwt/public.pem'));
$jwt = JWT::encode($payload, $privateKey, 'RS256');
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
CachedKeySet for OAuth providers (e.g., Auth0, Firebase):
use Firebase\JWT\CachedKeySet;
use Firebase\JWT\JWT;
$keySet = new CachedKeySet(
'https://your-provider.com/.well-known/jwks.json',
new \GuzzleHttp\Client(),
new \GuzzleHttp\Psr7\HttpFactory(),
\PhpFastCache\CacheManager::getInstance('files')
);
$decoded = JWT::decode($jwt, $keySet);
CachedKeySet in AppServiceProvider:
$this->app->singleton('jwks', function () {
return new CachedKeySet(
config('services.jwks_uri'),
new \GuzzleHttp\Client(),
new \GuzzleHttp\Psr7\HttpFactory(),
\PhpFastCache\CacheManager::getInstance('files')
);
});
$jwt = JWT::encode(
$payload,
$key,
'HS256',
null, // No custom header key
['jti' => 'unique-id-123', 'scope' => 'read:write']
);
$headers = new \stdClass();
JWT::decode($jwt, new Key($key, 'HS256'), $headers);
$scope = $headers->scope;
Mockery to test JWT validation:
$mockKey = Mockery::mock(\Firebase\JWT\Key::class);
$mockKey->shouldReceive('getKey')->andReturn('test-key');
$mockKey->shouldReceive('getAlgorithm')->andReturn('HS256');
$decoded = JWT::decode($jwt, $mockKey);
$this->assertEquals('expected_value', $decoded->claim);
$response = $this->withHeader('Authorization', 'Bearer ' . $jwt)
->get('/api/protected');
$response->assertStatus(200);
HS256) must be ≥32 characters. Use:
$key = Str::random(32); // Laravel helper
// ❌ Fails silently if algorithm is omitted!
$decoded = JWT::decode($jwt, $key); // Wrong!
// ✅ Correct
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
$leeway to account for clock differences (e.g., between servers):
JWT::$leeway = 60; // 1 minute
list($headerB64, $payloadB64, $signature) = explode('.', $jwt);
$header = json_decode(base64_decode($headerB64));
$payload = json_decode(base64_decode($payloadB64));
try {
$decoded = JWT::decode($jwt, $key);
} catch (\Firebase\JWT\ExpiredException $e) {
return response()->json(['error' => 'Token expired'], 401);
} catch (\Firebase\JWT\SignatureInvalidException $e) {
return response()->json(['error' => 'Invalid signature'], 401);
}
CachedKeySet to avoid repeated HTTP requests to JWKS endpoints.stdClass:
Decoded payloads are stdClass objects by default. Cast to array if needed:
$arrayPayload = (array) $decoded;
kid (key ID) claims to support multiple keys:
$jwt = JWT::encode($payload, $privateKey, 'RS256', 'kid-123');
$keys = [
'kid-123' => new Key($publicKey1, 'RS256'),
'kid-456' => new Key($publicKey2, 'RS256'),
];
$decoded = JWT::decode($jwt, $keys);
exp (expiration) claims to minimize exposure:
$payload['exp'] = now()->addMinutes(15)->timestamp;
none Algorithm:
Never use the none algorithm (disables signature verification).config/jwt.php:
return
How can I help you explore Laravel packages today?