codercat/jwk-to-pem
Convert RSA JSON Web Keys (JWK) to PEM public keys in PHP. Simple API via JWKConverter->toPEM() for turning JWK arrays into PEM strings, useful for verifying JWT signatures. Note: currently supports RSA keys only.
Installation:
composer require codercat/jwk-to-pem
Add to composer.json if using Laravel’s autoloader (handled automatically via Composer).
First Use Case:
Convert a JWK (e.g., from an OAuth provider or API response) to PEM for use with Laravel’s openssl_* functions or libraries like firebase/php-jwt or league/oauth2-server.
use CoderCat\JWKToPEM\JWKConverter;
$converter = new JWKConverter();
$jwk = json_decode(file_get_contents('path/to/jwk.json'), true);
$pem = $converter->toPEM($jwk);
Where to Look First:
JWKConverter.php (core logic).JWKConverterTest.php (edge cases like malformed JWKs).toPEM() method signature and RSA limitation.Fetch JWK: Retrieve JWK from an external source (e.g., OAuth provider’s JWKS endpoint) or static config.
$jwks = json_decode(file_get_contents('https://example.com/.well-known/jwks.json'), true);
Convert to PEM: Loop through JWKs (if multiple) and convert each to PEM. Cache results if the JWKS endpoint is static.
$converter = new JWKConverter();
$pems = collect($jwks['keys'])->map(fn($jwk) => $converter->toPEM($jwk));
Use PEM:
$client = new \GuzzleHttp\Client([
'cert' => $pem,
]);
firebase/php-jwt or league/oauth2-server.
$jwt = new \Firebase\JWT\JWT();
$decoded = $jwt->decode($token, $pem, ['RS256']);
Integration with Laravel Services:
$this->app->singleton(JWKConverter::class, fn() => new JWKConverter());
config/services.php and fetch/convert in a service class.Caching: Cache PEM outputs if the JWKS endpoint is static (e.g., using Laravel’s cache).
$pem = cache()->remember("jwk_{$jwk['kid']}_pem", now()->addHours(1), fn() =>
$converter->toPEM($jwk)
);
Dynamic JWKS Endpoints:
Use Laravel’s Http client to fetch JWKS dynamically and convert on-demand.
$jwks = Http::get('https://provider.com/.well-known/jwks.json')->json();
$pems = collect($jwks['keys'])->map(fn($jwk) => $converter->toPEM($jwk))->toArray();
Testing: Mock the converter in tests to avoid external dependencies.
$mockConverter = Mockery::mock(JWKConverter::class);
$mockConverter->shouldReceive('toPEM')->andReturn('mock-pem');
$this->app->instance(JWKConverter::class, $mockConverter);
RSA-Only Limitation: The package only supports RSA keys. Attempting to convert EC or other key types will throw an exception.
web-token/jwt-framework) for non-RSA keys or pre-convert JWKs to PEM externally.Malformed JWKs:
Missing or invalid fields (e.g., n, e for RSA) will cause failures. Validate JWKs before conversion:
if (!isset($jwk['kty'], $jwk['n'], $jwk['e'])) {
throw new \InvalidArgumentException('Invalid RSA JWK');
}
PEM Formatting:
The output includes \r\n line endings. Replace with \n if needed for specific use cases (e.g., some APIs expect Unix line endings).
$pem = str_replace("\r\n", "\n", $converter->toPEM($jwk));
Deprecated Methods:
Older versions had multipleToPEM() (renamed to toPEM() in v1.0). Ensure your code uses the current method.
Exception Handling: Wrap conversion in a try-catch to handle invalid JWKs gracefully:
try {
$pem = $converter->toPEM($jwk);
} catch (\Exception $e) {
\Log::error("JWK conversion failed: " . $e->getMessage());
throw new \RuntimeException('Failed to convert JWK to PEM');
}
Logging: Log JWK inputs for debugging:
\Log::debug('Converting JWK', ['jwk' => $jwk]);
Validation:
Use json_schema to validate JWKs before conversion:
composer require zendframework/zend-json-schema
$validator = new \Zend\JsonSchema\Validator();
$schema = json_decode(file_get_contents(__DIR__.'/rsa-jwk-schema.json'), true);
$validator->validate($jwk, $schema);
Custom PEM Headers:
Override the default BEGIN PUBLIC KEY/END PUBLIC KEY headers by extending JWKConverter:
class CustomJWKConverter extends JWKConverter {
protected function getHeader(): string {
return "-----BEGIN CUSTOM KEY-----";
}
protected function getFooter(): string {
return "-----END CUSTOM KEY-----";
}
}
Support for Private Keys:
The package outputs public keys only. To support private keys, extend the class to handle d, p, q, etc. fields (requires additional cryptographic logic).
Batch Processing: For large JWKS endpoints, process keys in chunks to avoid memory issues:
$chunkedJwks = array_chunk($jwks['keys'], 100);
foreach ($chunkedJwks as $chunk) {
$pems = array_map([$converter, 'toPEM'], $chunk);
// Store or use $pems
}
How can I help you explore Laravel packages today?