Skip to content

Commit

Permalink
Merge pull request #30 from SAP/fix_key_typing_error
Browse files Browse the repository at this point in the history
Fix key typing error
  • Loading branch information
Ynhockey authored Jun 19, 2023
2 parents c203229 + 02fd765 commit abeaa04
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 19 deletions.
50 changes: 32 additions & 18 deletions src/JWTUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

class JWTUtils
{
const RSA_ALG = 'RS256';

/**
* Composes a JWT to be used as a bearer token for authentication with Gigya
*
Expand All @@ -31,21 +33,22 @@ public static function getBearerToken(string $privateKey, string $userKey, strin
'jti' => $jti,
];

return JWT::encode($payload, $privateKey, 'RS256', $userKey);
return JWT::encode($payload, $privateKey, self::RSA_ALG, $userKey);
}

/**
* Validates JWT signature
*
* @param string $jwt
* @param string $apiKey
* @param string $apiDomain
* @param string $jwt The JWT to validate
* @param string $apiKey The API key of the site where the JWT is being validated
* @param string $apiDomain The API domain (data center) where the site is located. For global sites, use the primary data center.
* @param bool $ignoreCache If set to true, it will always contact Gigya in order to get the RSA public key. This could slow down performance considerably, and should not be used in production environments.
*
* @return stdClass|false
*
* @throws Exception
*/
public static function validateSignature(string $jwt, string $apiKey, string $apiDomain): stdClass|false
public static function validateSignature(string $jwt, string $apiKey, string $apiDomain, bool $ignoreCache = false): stdClass|false
{
/* Validate input and get KID */
if (!$jwt) {
Expand All @@ -63,15 +66,14 @@ public static function validateSignature(string $jwt, string $apiKey, string $ap
}

try {
$jwk = self::getJWKByKid($apiKey, $apiDomain, $kid);
$jwk = self::getJWKByKid($apiKey, $apiDomain, $kid, $ignoreCache);
} catch (GSException $e) {
return false;
}

try {
JWT::$leeway = 5;
$jwtInfo = JWT::decode($jwt, new Key($jwk, 'RS256'));
return $jwtInfo ?? false;
JWT::$leeway = 5;
return JWT::decode($jwt, $jwk);
} catch (UnexpectedValueException $e) {
return false;
}
Expand All @@ -81,20 +83,24 @@ public static function validateSignature(string $jwt, string $apiKey, string $ap
* @param string $apiKey
* @param string $apiDomain
* @param string $kid
* @param bool $ignoreCache
*
* @return Key|false
*
* @throws GSException
*/
private static function getJWKByKid(string $apiKey, string $apiDomain, string $kid): Key|false {
if (($jwks = self::readPublicKeyCache($apiDomain)) === false) {
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain);
private static function getJWKByKid(string $apiKey, string $apiDomain, string $kid, bool $ignoreCache = false): Key|false {
if (!$ignoreCache) {
$jwks = self::readPublicKeyCache($apiDomain);
if ($jwks === false) {
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain, $ignoreCache);
}
}

if (isset($jwks[$kid])) {
$jwk = $jwks[$kid];
} else {
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain);
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain, $ignoreCache);

if (isset($jwks[$kid])) {
$jwk = $jwks[$kid];
Expand All @@ -109,12 +115,13 @@ private static function getJWKByKid(string $apiKey, string $apiDomain, string $k
/**
* @param string $apiKey
* @param string $apiDomain
* @param bool $ignoreCache
*
* @return array<string, Key>|null
*
* @throws GSException
*/
private static function fetchPublicJWKs(string $apiKey, string $apiDomain): array|null
private static function fetchPublicJWKs(string $apiKey, string $apiDomain, bool $ignoreCache = false): array|null
{
$request = new GSRequest($apiKey, null, 'accounts.getJWTPublicKey');
$request->setAPIDomain($apiDomain);
Expand All @@ -130,7 +137,9 @@ private static function fetchPublicJWKs(string $apiKey, string $apiDomain): arra
throw new GSException('Unable to retrieve public key: ' . $e->getMessage());
}

self::addToPublicKeyCache($publicKeys, $apiDomain);
if (!$ignoreCache) {
self::addToPublicKeyCache($publicKeys, $apiDomain);
}

return $publicKeys;
}
Expand All @@ -139,8 +148,8 @@ private static function fetchPublicJWKs(string $apiKey, string $apiDomain): arra
}

/**
* @param array<Key> $publicKeys
* @param string $apiDomain
* @param array<Key> $publicKeys
* @param string $apiDomain
*
* @return int|false Bytes written to cache file or false on failure
*/
Expand Down Expand Up @@ -172,6 +181,11 @@ private static function readPublicKeyCache(string $apiDomain): array|false
return false;
}

return json_decode(file_get_contents($filename), true);
$jwks = json_decode(file_get_contents($filename), true);
array_walk($jwks, function(&$jwk) {
$jwk = new Key($jwk, self::RSA_ALG);
});

return $jwks;
}
}
17 changes: 16 additions & 1 deletion tests/JWTUtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,25 @@ public function testGetBearerTokenIncorrectPrivateKey()
*/
public function testValidateSignature(string $apiKey, string $apiDomain, string $userKey, string $privateKey, string $uid)
{
$cacheFilePath = __DIR__ . '/../src/keys/' . $apiDomain . '_keys.txt';
if (file_exists($cacheFilePath)) {
unlink($cacheFilePath);
}
$this->assertFileNotExists($cacheFilePath);

$jwt = $this->getJWT($apiKey, $apiDomain, $userKey, $privateKey, $uid);
$this->assertNotFalse($jwt);

$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain);
/* Requires at least two passes because getJWT behaves differently on the first pass, if the public key isn't cached. This is why ignoreCache is used */
$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain, true);
$this->assertEquals($claims->apiKey, $apiKey);
$this->assertEquals($claims->sub, $uid);
$this->assertNotEmpty($claims->email);

JWTUtils::validateSignature($jwt, $apiKey, $apiDomain); /* This one writes to the cache */
$this->assertFileExists($cacheFilePath);

$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain); /* This one validates against the cache */
$this->assertEquals($claims->apiKey, $apiKey);
$this->assertEquals($claims->sub, $uid);
$this->assertNotEmpty($claims->email);
Expand Down

0 comments on commit abeaa04

Please sign in to comment.