I'm trying to implement a federated login system with Google and OpenID Connect, and I'm stuck verifying and parsing the JWT id token I get back from Google. I'm following Google's documentation here.
Taking the advise of the docs, I'm trying to use an existing JWT library. The most popular PHP version on GitHub seems to be PHP_JWT. The problem appears to be in the format of the JWK keys.
Google's docs, linked above, say to get the keys from the jwks_uri
endpoint as shown in their discovery doc. That endpoint returns the following:
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "1771931eb0eb64eb97733e857685be153e079bb9",
"n": "AMNFQMNJw/EVwrYsyPTnEHWkaPinPb4ngc/SqD701aisFhbU9/wWoKADeFtwfBcWl1qjzIqhPorQElB+2mtiqUh3Qtaazt1x5wA9XnJDe6kjtMGm9nNLMilSVNBilAE8GIdbciMycISfOfL0WRaJrqpNxewNEVZjuYiGzOWahiDP",
"e": "AQAB"
},
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "7b3bc600209875d3c42ae277a0d018d1d21986ec",
"n": "AN2UvG5+hNEMIPIbnpPm+JQi6LFWXBPzg3Ltb3xkVmSTjVaCFWppw/ZYRBgpToGKZP9XJstlOE88SDUFSMZIkIqtLpnUqmZax2Zc2gjEB9PhmHSH3/tTmtZ1U0X6V+crqitZ2uc3NV78vCn9/s+WuPwk/gfKBG8Cirb0fgLmsPd9",
"e": "AQAB"
}
]
}
Looking at the source for the JWT class's decode
and verify
methods, it seems like the $keys
parameter can be an array, but they expect the array keys to be the kid
and the array values to be: @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
. It's simple enough to pull out the kid
properties and use them as array keys, but what should be used as the array values?
From Google's JWKs document, it looks like we're using RS*
, but I have no idea what part of the key object is the resource of an openssl public key
. I've tried using the entire stdClass object and just the n
string, but both fail at the openssl_verify
step. That function emits a notice saying: "Warning: openssl_verify(): supplied key param cannot be coerced into a public key".
So, obviously, I'm passing in the wrong key, but what is the right key?
Google's library for this seems to use a different endpoint to get the keys. That endpoint seems to return an array of certs. Do I need to use something like that instead? If so, why would the docs tell you to use the jwks_uri
endpoint?
For the people trying to do this in the future I'd like to provide a full answer;
The n
and e
parts in the jwks_uri
JSON keys give the modulus and the exponent, which can be used to retrieve the public key (which is all that is required to verify the signature). How this is done in pure PHP is detailed in this post:
openssl: how can i get public key from modulus
However, you should be aware that the JSON document provided at Google's jwks_uri
uses URL safe base64 encoding (i.e. normal base64 with the + and / characters replaced). Failing to take this into account might still give you an invalid certificate. Since you mention using php-jwt
, working code to get the public key from the modulus and the exponent using phpseclib is:
$modulus = 'someencodedmodulusvalue';
$exponent = 'someencodedexponentvalue';
$rsa = new Crypt_RSA();
$modulus = new Math_BigInteger(JWT::urlsafeB64Decode($modulus), 256);
$exponent = new Math_BigInteger(JWT::urlsafeB64Decode($exponent), 256);
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$rsa->setPublicKey();
$pubKey = $rsa->getPublicKey();
Both endpoints contain the same informations but in different format.
The jwks_uri
endpoint give you the modulus and exponent value of the RSA public key. You can use these two values to generate the PEM file you get at https://www.googleapis.com/oauth2/v1/certs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With