Note: I read this topic and went through the Laravel quick start: Laravel API. Should I manually verify access token's signature?
Hello everyone,
I was seeking a dynamic jwt verification solution for PHP, I came across jose-php which allowed me to:
convert JWKS to PEM => Decode JWT => verify Data against PEM
I will then apply other custom middlewares:
- Expiration check on the token
- Validation of Scope & Audience
Here is a sample code of verification middleware with the dummy data:
# public key retrieved from JWKS endpoint
$components = array(
'kty' => 'RSA',
'e' => 'AQAB',
'n' => 'x9vNhcvSrxjsegZAAo4OEuo...'
);
$public_key= JOSE_JWK::decode($components);
$jwt_string = 'eyJ...'; // Access_token
$jws = JOSE_JWT::decode($jwt_string);
$result = $jws->verify($public_key, 'RS256');
I am currently getting undefined on $result, Which I am also looking into it.
My question is, is this a valid way or I am exposing a vulnerability by doing such a thing, and if there is any other potential better way that does not use the Client SDKs?
Just an update on the process, I switched to firebase/php-jwt as it is more convenient and straightforward to use and it was fairly easier to go quickly through its code and it does not return undefined anymore. Now the middleware code for validation looks like below:
$jwks = ['keys' => [[], []];
// JWK::parseKeySet($jwks) returns an associative array of **kid** to private
// key. Pass this as the second parameter to JWT::decode.
// Instead of RS256 use your own algo
// $data can return error so wrap it in try catch and do as you desire afterward
$data= (array) JWT::decode("YOUR_ACCESS_TOKEN", JWK::parseKeySet($jwks), ['RS256', 'RS256']);
For those who are willing to test a sample encoding and decoding process, feel free to use the private key and public key below: (Credit to firebase documentation with a bit of tweaking on my side to convert it to a simple Laravel controller)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use \Firebase\JWT\JWT;
use \Firebase\JWT\JWK;
use Illuminate\Support\Facades\Http;
class JWTValidation extends Controller
{
public function bundle(){
$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
-----END RSA PRIVATE KEY-----
EOD;
$publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
ehde/zUxo6UvS7UrBQIDAQAB
-----END PUBLIC KEY-----
EOD;
$payload = array(
"iss" => "example.org",
"aud" => "example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);
$jwt = JWT::encode($payload, $privateKey, 'RS256');
//echo "Encode:\n" . print_r($jwt, true) . "\n";
$decoded = JWT::decode($jwt, $publicKey, array('RS256'));
/*
NOTE: This will now be an object instead of an associative array. To get
an associative array, you will need to cast it as such:
*/
$decoded_array = (array) $decoded;
return response()->json(['jwt' => $jwt, 'decoded' => $decoded]);
//echo "Decode:\n" . print_r($decoded_array, true) . "\n";
}
}
Now back to my first question again
In case that I validate the key with the help of this library as the first piece of the code, am I exposing any vulnerability? or will it be a time-consuming task in long run to maintain a custom verification flow like this?