Introduction
When working with JWT authentication in Laravel, you may encounter the error:
openssl_verify(): Supplied key param cannot be coerced into a public key
This typically happens when verifying an RS256-signed JWT with an incorrect or improperly formatted public key. In this guide, we’ll walk through the steps to extract and use the correct RSA public key for JWT verification.
Understanding the Issue
JWT Header Inspection
Before solving the issue, inspect the JWT header to determine the signing algorithm:
echo "YOUR_JWT_TOKEN_HERE" | cut -d "." -f1 | base64 --decode
If you see something like this:
{
"alg": "RS256",
"kid": "public:01fa2927-9677-42bb-9233-fa8f68f261fc"
}
"alg": "RS256"
means the token is signed using RSA encryption, requiring a public key for verification."kid"
(Key ID) helps locate the correct public key.
Finding the JWKS (JSON Web Key Set) URL
Public keys for JWT verification are often stored in a JWKS endpoint. If your JWT includes an iss
(issuer) field like:
"iss": "https://id-int-hydra.dev.local/"
Try accessing the JWKS URL:
https://id-int-hydra.dev.local/.well-known/jwks.json
Use this command to retrieve the public key information:
curl -s https://id-int-hydra.dev.local/.well-known/jwks.json | jq
Extracting the RSA Public Key from JWKS
If the JWKS response contains:
{
"keys": [
{
"kid": "public:01fa2927-9677-42bb-9233-fa8f68f261fc",
"kty": "RSA",
"alg": "RS256",
"n": "base64url-encoded-key",
"e": "AQAB"
}
]
}
You need to convert the n
and e
values to PEM format.
Python Script to Convert JWKS to PEM
Create a script (convert_jwks_to_pem.py
) with the following code:
import json
import base64
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# Example JWKS response
jwks = {
"keys": [
{
"kid": "public:01fa2927-9677-42bb-9233-fa8f68f261fc",
"kty": "RSA",
"alg": "RS256",
"n": "base64url_encoded_n_value",
"e": "AQAB"
}
]
}
def base64url_decode(input):
input += '=' * (4 - (len(input) % 4)) # Pad correctly
return base64.urlsafe_b64decode(input)
key = jwks["keys"][0]
modulus = int.from_bytes(base64url_decode(key["n"]), byteorder='big')
exponent = int.from_bytes(base64url_decode(key["e"]), byteorder='big')
public_key = rsa.RSAPublicNumbers(exponent, modulus).public_key(default_backend())
pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(pem.decode())
Run the script to generate a valid RSA public key in PEM format:
python convert_jwks_to_pem.py > public_key.pem
Verifying the Public Key
To confirm the validity of the generated public key, run:
openssl rsa -in public_key.pem -pubin -text
If successful, you should see details about the RSA key structure.
Using the Public Key in Laravel
1️⃣ Store the Public Key in .env
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqh...
-----END PUBLIC KEY-----"
2️⃣ Update Laravel Configuration (config/auth.php
)
'jwt_public_key' => env('JWT_PUBLIC_KEY'),
3️⃣ Modify JWT Verification in Laravel
Modify your controller to load the correct key:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$publicKey = config('auth.jwt_public_key');
$decoded = JWT::decode($token, new Key($publicKey, 'RS256'));
Conclusion
By following these steps, you can successfully extract, verify, and use an RSA public key for JWT authentication in Laravel. This ensures secure and correct token verification in your application.
Let me know in the comments if you have any questions or need further clarification! 🚀