# Firebase PHP-JWT Library Firebase PHP-JWT is a simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to RFC 7519. This library provides a secure way to create and validate JWTs using various cryptographic algorithms including HMAC, RSA, ECDSA, and EdDSA signatures. It supports multiple key formats including raw keys, PEM-encoded keys, JWK (JSON Web Keys), and JWKS (JSON Web Key Sets) with optional caching capabilities. The library is designed for authentication and authorization workflows where stateless tokens need to be securely transmitted between parties. It includes built-in validation of standard JWT claims (iss, aud, iat, nbf, exp), automatic clock skew handling, and comprehensive exception handling for different failure scenarios. The library requires PHP 8.0 or higher and optionally supports libsodium for EdDSA signatures. ## API Reference ### Encoding JWT with HMAC (HS256) Create and sign a JWT token using a symmetric HMAC key with SHA-256 algorithm. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; $key = 'your-256-bit-secret-key-min-32-chars'; $payload = [ 'iss' => 'https://example.com', // Issuer 'aud' => 'https://api.example.com', // Audience 'iat' => time(), // Issued at 'nbf' => time(), // Not before 'exp' => time() + 3600, // Expiration (1 hour) 'sub' => 'user123', // Subject 'data' => [ 'userId' => 123, 'role' => 'admin' ] ]; try { $jwt = JWT::encode($payload, $key, 'HS256'); echo "Generated JWT: " . $jwt . "\n"; // Output: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOi... } catch (Exception $e) { echo "Encoding failed: " . $e->getMessage(); } ``` ### Decoding JWT with HMAC (HS256) Validate and decode a JWT token, verifying signature and claims. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; use Firebase\JWT\ExpiredException; use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; $key = 'your-256-bit-secret-key-min-32-chars'; $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOi...'; try { // Set leeway for clock skew (optional, in seconds) JWT::$leeway = 60; // Decode and verify $decoded = JWT::decode($jwt, new Key($key, 'HS256')); // Access claims echo "Issuer: " . $decoded->iss . "\n"; echo "User ID: " . $decoded->data->userId . "\n"; echo "Role: " . $decoded->data->role . "\n"; // Convert to array if needed $decodedArray = (array) $decoded; print_r($decodedArray); } catch (ExpiredException $e) { echo "Token has expired: " . $e->getMessage(); } catch (SignatureInvalidException $e) { echo "Signature verification failed: " . $e->getMessage(); } catch (BeforeValidException $e) { echo "Token not yet valid: " . $e->getMessage(); } catch (UnexpectedValueException $e) { echo "Invalid token: " . $e->getMessage(); } catch (Exception $e) { echo "Decoding error: " . $e->getMessage(); } ``` ### Encoding and Decoding JWT with RSA (RS256) Create and verify JWT tokens using RSA asymmetric cryptography with SHA-256. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; $privateKey = << 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'exp' => time() + 3600, 'sub' => 'user456' ]; try { // Encode with private key $jwt = JWT::encode($payload, $privateKey, 'RS256'); echo "Encoded JWT: " . $jwt . "\n"; // Decode with public key $decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); echo "Subject: " . $decoded->sub . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Using RSA Key with Passphrase Load and use password-protected RSA private keys for signing JWTs. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; $passphrase = 'your-secure-passphrase'; $privateKeyFile = '/path/to/key-with-passphrase.pem'; try { // Load private key with passphrase $privateKey = openssl_pkey_get_private( file_get_contents($privateKeyFile), $passphrase ); if ($privateKey === false) { throw new Exception('Failed to load private key: ' . openssl_error_string()); } $payload = [ 'iss' => 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'exp' => time() + 3600, 'data' => ['action' => 'update'] ]; // Encode with passphrase-protected key $jwt = JWT::encode($payload, $privateKey, 'RS256'); echo "Encoded JWT: " . $jwt . "\n"; // Extract public key from private key for verification $publicKey = openssl_pkey_get_details($privateKey)['key']; // Decode and verify $decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); echo "Action: " . $decoded->data->action . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Encoding and Decoding with EdDSA (Ed25519) Use Edwards-curve Digital Signature Algorithm for compact and fast signatures. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; try { // Generate Ed25519 key pair (requires libsodium) $keyPair = sodium_crypto_sign_keypair(); $privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); $publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); $payload = [ 'iss' => 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'exp' => time() + 3600, 'sub' => 'user789', 'scope' => 'read:profile write:posts' ]; // Encode with private key $jwt = JWT::encode($payload, $privateKey, 'EdDSA'); echo "Encoded JWT: " . $jwt . "\n"; // Decode with public key $decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); echo "Subject: " . $decoded->sub . "\n"; echo "Scope: " . $decoded->scope . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Using Multiple Keys with Key IDs Manage multiple signing keys and select the appropriate key using Key ID (kid). ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; // Setup multiple keys $privateKeyRS256 = '-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----'; $publicKeyRS256 = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----'; $privateKeyEdDSA = base64_encode(sodium_crypto_sign_secretkey(sodium_crypto_sign_keypair())); $publicKeyEdDSA = base64_encode(sodium_crypto_sign_publickey(sodium_crypto_sign_keypair())); $payload = [ 'iss' => 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'exp' => time() + 3600, 'data' => ['role' => 'user'] ]; try { // Encode with different keys and key IDs $jwt1 = JWT::encode($payload, $privateKeyRS256, 'RS256', 'key-2024-rs256'); $jwt2 = JWT::encode($payload, $privateKeyEdDSA, 'EdDSA', 'key-2024-eddsa'); echo "JWT 1 (RS256): " . $jwt1 . "\n"; echo "JWT 2 (EdDSA): " . $jwt2 . "\n"; // Create key set for decoding $keys = [ 'key-2024-rs256' => new Key($publicKeyRS256, 'RS256'), 'key-2024-eddsa' => new Key($publicKeyEdDSA, 'EdDSA'), ]; // Decode with automatic key selection based on kid header $decoded1 = JWT::decode($jwt1, $keys); $decoded2 = JWT::decode($jwt2, $keys); echo "Decoded 1 role: " . $decoded1->data->role . "\n"; echo "Decoded 2 role: " . $decoded2->data->role . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Adding Custom Headers to JWT Include custom header fields in the JWT token during encoding. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; $key = 'your-256-bit-secret-key-min-32-chars'; $payload = [ 'iss' => 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'exp' => time() + 3600, 'data' => ['userId' => 123] ]; $customHeaders = [ 'x-tenant-id' => 'tenant-123', 'x-api-version' => 'v2' ]; try { // Encode with custom headers $jwt = JWT::encode($payload, $key, 'HS256', null, $customHeaders); echo "JWT with custom headers: " . $jwt . "\n"; // Decode and capture headers $headers = new stdClass(); $decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers); echo "Algorithm: " . $headers->alg . "\n"; echo "Type: " . $headers->typ . "\n"; echo "Tenant ID: " . $headers->{'x-tenant-id'} . "\n"; echo "API Version: " . $headers->{'x-api-version'} . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Parsing JWK (JSON Web Key) Convert a JWK JSON structure into a Key object for JWT validation. ```php use Firebase\JWT\JWK; use Firebase\JWT\JWT; // Example JWK for RSA public key $jwk = [ 'kty' => 'RSA', 'use' => 'sig', 'alg' => 'RS256', 'kid' => 'my-key-id', 'n' => 'xGOr-H7A-PWj...', // RSA modulus (base64url encoded) 'e' => 'AQAB' // RSA exponent (base64url encoded) ]; try { // Parse single JWK into Key object $key = JWK::parseKey($jwk); echo "Algorithm: " . $key->getAlgorithm() . "\n"; // Use the key to decode a JWT $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9...'; $decoded = JWT::decode($jwt, $key); print_r($decoded); } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Parsing JWKS (JSON Web Key Set) Parse a complete JWKS containing multiple keys for multi-key JWT validation. ```php use Firebase\JWT\JWK; use Firebase\JWT\JWT; // JWKS response (typically from /.well-known/jwks.json endpoint) $jwks = [ 'keys' => [ [ 'kty' => 'RSA', 'use' => 'sig', 'alg' => 'RS256', 'kid' => 'key-2024-01', 'n' => 'xGOr-H7A...', 'e' => 'AQAB' ], [ 'kty' => 'EC', 'use' => 'sig', 'alg' => 'ES256', 'kid' => 'key-2024-02', 'crv' => 'P-256', 'x' => '4c_cRaXS...', 'y' => 'nB2Xc4C8...' ] ] ]; try { // Parse entire JWKS into array of Key objects $keys = JWK::parseKeySet($jwks); echo "Loaded " . count($keys) . " keys\n"; // Decode JWT with automatic key selection $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0yMDI0LTAxIn0...'; $decoded = JWT::decode($jwt, $keys); echo "Successfully decoded JWT\n"; print_r($decoded); } catch (InvalidArgumentException $e) { echo "JWKS is empty or invalid: " . $e->getMessage(); } catch (UnexpectedValueException $e) { echo "JWKS format error: " . $e->getMessage(); } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Using Cached Key Set from Remote JWKS URI Fetch and cache JWKS from a remote URI with automatic refresh and rate limiting. ```php use Firebase\JWT\CachedKeySet; use Firebase\JWT\JWT; // Setup PSR-compatible HTTP client and cache $httpClient = new GuzzleHttp\Client(); $httpFactory = new GuzzleHttp\Psr\HttpFactory(); $cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); $jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; try { // Create cached key set $keySet = new CachedKeySet( $jwksUri, // JWKS URI $httpClient, // PSR-18 HTTP client $httpFactory, // PSR-17 HTTP request factory $cacheItemPool, // PSR-6 cache pool 300, // Cache expiration (seconds), null for no expiry true // Enable rate limiting (max 10 requests/minute) ); // Decode JWT - automatically fetches, caches, and refreshes keys as needed $jwt = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...'; $decoded = JWT::decode($jwt, $keySet); echo "Token issuer: " . $decoded->iss . "\n"; echo "Token subject: " . $decoded->sub . "\n"; echo "Expires at: " . date('Y-m-d H:i:s', $decoded->exp) . "\n"; } catch (UnexpectedValueException $e) { echo "Key fetch error: " . $e->getMessage(); } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Comprehensive Exception Handling Handle all possible JWT validation and decoding exceptions with specific error cases. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use Firebase\JWT\ExpiredException; use DomainException; use InvalidArgumentException; use UnexpectedValueException; $key = 'your-256-bit-secret-key-min-32-chars'; $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'; try { $decoded = JWT::decode($jwt, new Key($key, 'HS256')); echo "Token validated successfully\n"; echo "User: " . $decoded->sub . "\n"; } catch (InvalidArgumentException $e) { // Key is empty or malformed echo "Invalid key provided: " . $e->getMessage(); http_response_code(500); } catch (DomainException $e) { // Algorithm unsupported, key invalid, or OpenSSL/libsodium error echo "Configuration error: " . $e->getMessage(); http_response_code(500); } catch (SignatureInvalidException $e) { // JWT signature verification failed - token tampered or wrong key echo "Invalid signature: " . $e->getMessage(); http_response_code(401); } catch (BeforeValidException $e) { // Token used before 'nbf' (not before) or 'iat' (issued at) time echo "Token not yet valid: " . $e->getMessage(); http_response_code(401); // Access payload even from expired/invalid token (use with caution) if (method_exists($e, 'getPayload')) { $payload = $e->getPayload(); echo "Token becomes valid at: " . date('Y-m-d H:i:s', $payload->nbf); } } catch (ExpiredException $e) { // Token expired according to 'exp' claim echo "Token expired: " . $e->getMessage(); http_response_code(401); // Access payload from expired token if (method_exists($e, 'getPayload')) { $payload = $e->getPayload(); echo "Token expired at: " . date('Y-m-d H:i:s', $payload->exp); } } catch (UnexpectedValueException $e) { // JWT malformed, missing algorithm, algorithm mismatch, or invalid kid echo "Invalid token format: " . $e->getMessage(); http_response_code(400); } catch (Exception $e) { // Catch-all for unexpected errors echo "Unexpected error: " . $e->getMessage(); http_response_code(500); } ``` ### Working with Leeway and Custom Timestamps Configure clock skew tolerance and override current timestamp for testing. ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; $key = 'your-256-bit-secret-key-min-32-chars'; // Set leeway to account for clock skew between servers // Recommended: keep this small (60 seconds or less) JWT::$leeway = 60; // Adds 60 seconds tolerance to time-based claims $payload = [ 'iss' => 'https://example.com', 'aud' => 'https://api.example.com', 'iat' => time(), 'nbf' => time() + 30, // Valid 30 seconds from now 'exp' => time() + 3600, 'sub' => 'user123' ]; try { $jwt = JWT::encode($payload, $key, 'HS256'); // This would normally fail (nbf is in future), but leeway allows it $decoded = JWT::decode($jwt, new Key($key, 'HS256')); echo "Decoded with leeway: " . $decoded->sub . "\n"; // Override timestamp for testing (useful in unit tests) JWT::$timestamp = time() + 7200; // Simulate 2 hours in future try { $decoded = JWT::decode($jwt, new Key($key, 'HS256')); } catch (ExpiredException $e) { echo "Token correctly detected as expired in simulated future time\n"; } // Reset timestamp to use system time JWT::$timestamp = null; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ``` ### Base64URL Encoding and Decoding Use JWT's URL-safe Base64 encoding utilities for custom implementations. ```php use Firebase\JWT\JWT; $data = 'Hello, World! This is a test string with special chars: +/='; try { // URL-safe Base64 encode (replaces +/ with -_ and removes padding) $encoded = JWT::urlsafeB64Encode($data); echo "Encoded: " . $encoded . "\n"; // Output: SGVsbG8sIFdvcmxkISBUaGlzIGlzIGEgdGVzdCBzdHJpbmcgd2l0aCBzcGVjaWFsIGNoYXJzOiArLz0 // URL-safe Base64 decode $decoded = JWT::urlsafeB64Decode($encoded); echo "Decoded: " . $decoded . "\n"; // Output: Hello, World! This is a test string with special chars: +/= // Convert between standard Base64 and URL-safe Base64 $standardB64 = 'SGVsbG8+Pz8/Pz8='; $urlSafeB64 = JWT::convertBase64UrlToBase64($standardB64); echo "Converted: " . $urlSafeB64 . "\n"; } catch (InvalidArgumentException $e) { echo "Invalid Base64 string: " . $e->getMessage(); } ``` ### Creating Key Objects Instantiate Key objects for various key types and algorithms. ```php use Firebase\JWT\Key; try { // HMAC symmetric key (string) $hmacKey = new Key('your-secret-key-string', 'HS256'); // RSA public key (PEM string) $rsaPublicKeyPem = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----'; $rsaKey = new Key($rsaPublicKeyPem, 'RS256'); // OpenSSL resource from file $privateKeyResource = openssl_pkey_get_private( file_get_contents('/path/to/private.pem'), 'passphrase' ); $rsaKeyFromResource = new Key($privateKeyResource, 'RS256'); // EdDSA key (Base64 encoded) $eddsaPublicKey = base64_encode(sodium_crypto_sign_publickey(sodium_crypto_sign_keypair())); $eddsaKey = new Key($eddsaPublicKey, 'EdDSA'); // EC key for ES256 $ecPublicKeyPem = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----'; $ecKey = new Key($ecPublicKeyPem, 'ES256'); // Access key properties echo "Algorithm: " . $hmacKey->getAlgorithm() . "\n"; } catch (TypeError $e) { echo "Invalid key material type: " . $e->getMessage(); } catch (InvalidArgumentException $e) { echo "Key error: " . $e->getMessage(); } ``` ## Summary Firebase PHP-JWT provides a robust, standards-compliant implementation for JWT token handling in PHP applications. The primary use cases include API authentication where tokens are issued by an authorization server and validated by resource servers, single sign-on (SSO) implementations where identity providers issue tokens for service providers, and stateless session management where user session data is encoded directly in the token. The library excels in microservices architectures where services need to verify tokens without calling back to a central authentication service, and in mobile/SPA applications where tokens are stored client-side and included in API requests. Integration patterns typically involve encoding JWTs during login (creating access and refresh tokens), validating JWTs in API middleware or route guards to protect endpoints, using JWK/JWKS for key rotation in production environments (especially with OAuth2/OIDC providers like Google, Auth0, or Okta), and implementing CachedKeySet to minimize network calls when fetching public keys from remote URIs. The library's support for multiple algorithms makes it suitable for various security requirements, from simple HMAC-based tokens for internal APIs to RSA/ECDSA signatures for public-facing services requiring key rotation and distribution.