Extracting and Using an RSA Public Key for JWT Verification in Laravel

0saves

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! 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *