Skip to content

Commit abeaa04

Browse files
authored
Merge pull request #30 from SAP/fix_key_typing_error
Fix key typing error
2 parents c203229 + 02fd765 commit abeaa04

File tree

2 files changed

+48
-19
lines changed

2 files changed

+48
-19
lines changed

src/JWTUtils.php

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
class JWTUtils
1414
{
15+
const RSA_ALG = 'RS256';
16+
1517
/**
1618
* Composes a JWT to be used as a bearer token for authentication with Gigya
1719
*
@@ -31,21 +33,22 @@ public static function getBearerToken(string $privateKey, string $userKey, strin
3133
'jti' => $jti,
3234
];
3335

34-
return JWT::encode($payload, $privateKey, 'RS256', $userKey);
36+
return JWT::encode($payload, $privateKey, self::RSA_ALG, $userKey);
3537
}
3638

3739
/**
3840
* Validates JWT signature
3941
*
40-
* @param string $jwt
41-
* @param string $apiKey
42-
* @param string $apiDomain
42+
* @param string $jwt The JWT to validate
43+
* @param string $apiKey The API key of the site where the JWT is being validated
44+
* @param string $apiDomain The API domain (data center) where the site is located. For global sites, use the primary data center.
45+
* @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.
4346
*
4447
* @return stdClass|false
4548
*
4649
* @throws Exception
4750
*/
48-
public static function validateSignature(string $jwt, string $apiKey, string $apiDomain): stdClass|false
51+
public static function validateSignature(string $jwt, string $apiKey, string $apiDomain, bool $ignoreCache = false): stdClass|false
4952
{
5053
/* Validate input and get KID */
5154
if (!$jwt) {
@@ -63,15 +66,14 @@ public static function validateSignature(string $jwt, string $apiKey, string $ap
6366
}
6467

6568
try {
66-
$jwk = self::getJWKByKid($apiKey, $apiDomain, $kid);
69+
$jwk = self::getJWKByKid($apiKey, $apiDomain, $kid, $ignoreCache);
6770
} catch (GSException $e) {
6871
return false;
6972
}
7073

7174
try {
72-
JWT::$leeway = 5;
73-
$jwtInfo = JWT::decode($jwt, new Key($jwk, 'RS256'));
74-
return $jwtInfo ?? false;
75+
JWT::$leeway = 5;
76+
return JWT::decode($jwt, $jwk);
7577
} catch (UnexpectedValueException $e) {
7678
return false;
7779
}
@@ -81,20 +83,24 @@ public static function validateSignature(string $jwt, string $apiKey, string $ap
8183
* @param string $apiKey
8284
* @param string $apiDomain
8385
* @param string $kid
86+
* @param bool $ignoreCache
8487
*
8588
* @return Key|false
8689
*
8790
* @throws GSException
8891
*/
89-
private static function getJWKByKid(string $apiKey, string $apiDomain, string $kid): Key|false {
90-
if (($jwks = self::readPublicKeyCache($apiDomain)) === false) {
91-
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain);
92+
private static function getJWKByKid(string $apiKey, string $apiDomain, string $kid, bool $ignoreCache = false): Key|false {
93+
if (!$ignoreCache) {
94+
$jwks = self::readPublicKeyCache($apiDomain);
95+
if ($jwks === false) {
96+
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain, $ignoreCache);
97+
}
9298
}
9399

94100
if (isset($jwks[$kid])) {
95101
$jwk = $jwks[$kid];
96102
} else {
97-
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain);
103+
$jwks = self::fetchPublicJWKs($apiKey, $apiDomain, $ignoreCache);
98104

99105
if (isset($jwks[$kid])) {
100106
$jwk = $jwks[$kid];
@@ -109,12 +115,13 @@ private static function getJWKByKid(string $apiKey, string $apiDomain, string $k
109115
/**
110116
* @param string $apiKey
111117
* @param string $apiDomain
118+
* @param bool $ignoreCache
112119
*
113120
* @return array<string, Key>|null
114121
*
115122
* @throws GSException
116123
*/
117-
private static function fetchPublicJWKs(string $apiKey, string $apiDomain): array|null
124+
private static function fetchPublicJWKs(string $apiKey, string $apiDomain, bool $ignoreCache = false): array|null
118125
{
119126
$request = new GSRequest($apiKey, null, 'accounts.getJWTPublicKey');
120127
$request->setAPIDomain($apiDomain);
@@ -130,7 +137,9 @@ private static function fetchPublicJWKs(string $apiKey, string $apiDomain): arra
130137
throw new GSException('Unable to retrieve public key: ' . $e->getMessage());
131138
}
132139

133-
self::addToPublicKeyCache($publicKeys, $apiDomain);
140+
if (!$ignoreCache) {
141+
self::addToPublicKeyCache($publicKeys, $apiDomain);
142+
}
134143

135144
return $publicKeys;
136145
}
@@ -139,8 +148,8 @@ private static function fetchPublicJWKs(string $apiKey, string $apiDomain): arra
139148
}
140149

141150
/**
142-
* @param array<Key> $publicKeys
143-
* @param string $apiDomain
151+
* @param array<Key> $publicKeys
152+
* @param string $apiDomain
144153
*
145154
* @return int|false Bytes written to cache file or false on failure
146155
*/
@@ -172,6 +181,11 @@ private static function readPublicKeyCache(string $apiDomain): array|false
172181
return false;
173182
}
174183

175-
return json_decode(file_get_contents($filename), true);
184+
$jwks = json_decode(file_get_contents($filename), true);
185+
array_walk($jwks, function(&$jwk) {
186+
$jwk = new Key($jwk, self::RSA_ALG);
187+
});
188+
189+
return $jwks;
176190
}
177191
}

tests/JWTUtilsTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,25 @@ public function testGetBearerTokenIncorrectPrivateKey()
5858
*/
5959
public function testValidateSignature(string $apiKey, string $apiDomain, string $userKey, string $privateKey, string $uid)
6060
{
61+
$cacheFilePath = __DIR__ . '/../src/keys/' . $apiDomain . '_keys.txt';
62+
if (file_exists($cacheFilePath)) {
63+
unlink($cacheFilePath);
64+
}
65+
$this->assertFileNotExists($cacheFilePath);
66+
6167
$jwt = $this->getJWT($apiKey, $apiDomain, $userKey, $privateKey, $uid);
6268
$this->assertNotFalse($jwt);
6369

64-
$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain);
70+
/* 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 */
71+
$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain, true);
72+
$this->assertEquals($claims->apiKey, $apiKey);
73+
$this->assertEquals($claims->sub, $uid);
74+
$this->assertNotEmpty($claims->email);
75+
76+
JWTUtils::validateSignature($jwt, $apiKey, $apiDomain); /* This one writes to the cache */
77+
$this->assertFileExists($cacheFilePath);
78+
79+
$claims = JWTUtils::validateSignature($jwt, $apiKey, $apiDomain); /* This one validates against the cache */
6580
$this->assertEquals($claims->apiKey, $apiKey);
6681
$this->assertEquals($claims->sub, $uid);
6782
$this->assertNotEmpty($claims->email);

0 commit comments

Comments
 (0)