From 7596a95fea5e4087ce36d5cbc03bc916cb2933fd Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 9 Nov 2021 12:39:42 -0800 Subject: [PATCH] fix: require key object --- README.md | 3 +- src/JWK.php | 10 +++- src/JWT.php | 64 ++++++++---------------- tests/JWKTest.php | 26 ++++++++-- tests/JWTTest.php | 114 +++++++++++++++--------------------------- tests/rsa-jwkset.json | 7 +-- 6 files changed, 98 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 1d392cd1..28a84ae6 100644 --- a/README.md +++ b/README.md @@ -200,8 +200,7 @@ $jwks = ['keys' => []]; // JWK::parseKeySet($jwks) returns an associative array of **kid** to private // key. Pass this as the second parameter to JWT::decode. -// NOTE: The deprecated $supportedAlgorithm must be supplied when parsing from JWK. -JWT::decode($payload, JWK::parseKeySet($jwks), $supportedAlgorithm); +JWT::decode($payload, JWK::parseKeySet($jwks)); ``` Changelog diff --git a/src/JWK.php b/src/JWK.php index 981a9ba7..c53251d3 100644 --- a/src/JWK.php +++ b/src/JWK.php @@ -47,7 +47,15 @@ public static function parseKeySet(array $jwks) foreach ($jwks['keys'] as $k => $v) { $kid = isset($v['kid']) ? $v['kid'] : $k; if ($key = self::parseKey($v)) { - $keys[$kid] = $key; + if (isset($v['alg'])) { + $keys[$kid] = new Key($key, $v['alg']); + } else { + // The "alg" parameter is optional in a KTY, but is required + // for parsing in this library. Add it manually to your JWK + // array if it doesn't already exist. + // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + throw new InvalidArgumentException('JWK key is missing "alg"'); + } } } diff --git a/src/JWT.php b/src/JWT.php index ec1641bc..65c20eb7 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -60,13 +60,11 @@ class JWT * Decodes a JWT string into a PHP object. * * @param string $jwt The JWT - * @param Key|array|mixed $keyOrKeyArray The Key or array of Key objects. + * @param Key|array $keyOrKeyArray The Key or array of Key objects. * If the algorithm used is asymmetric, this is the public key * Each Key object contains an algorithm and matching key. * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', * 'HS512', 'RS256', 'RS384', and 'RS512' - * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only - * should be used for backwards compatibility. * * @return object The JWT's payload as a PHP object * @@ -80,8 +78,9 @@ class JWT * @uses jsonDecode * @uses urlsafeB64Decode */ - public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array()) + public static function decode($jwt, $keyOrKeyArray) { + // Validate JWT $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; if (empty($keyOrKeyArray)) { @@ -108,31 +107,18 @@ public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array( throw new UnexpectedValueException('Algorithm not supported'); } - list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm( - $keyOrKeyArray, - empty($header->kid) ? null : $header->kid - ); + $key = self::getKey($keyOrKeyArray, empty($header->kid) ? null : $header->kid); - if (empty($algorithm)) { - // Use deprecated "allowed_algs" to determine if the algorithm is supported. - // This opens up the possibility of an attack in some implementations. - // @see https://github.com/firebase/php-jwt/issues/351 - if (!\in_array($header->alg, $allowed_algs)) { - throw new UnexpectedValueException('Algorithm not allowed'); - } - } else { - // Check the algorithm - if (!self::constantTimeEquals($algorithm, $header->alg)) { - // See issue #351 - throw new UnexpectedValueException('Incorrect key for this algorithm'); - } + // Check the algorithm + if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); } if ($header->alg === 'ES256' || $header->alg === 'ES384') { // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures $sig = self::signatureToDER($sig); } - - if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) { + if (!static::verify("$headb64.$bodyb64", $sig, $key->getKeyMaterial(), $header->alg)) { throw new SignatureInvalidException('Signature verification failed'); } @@ -393,21 +379,21 @@ public static function urlsafeB64Encode($input) * * @return array containing the keyMaterial and algorithm */ - private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) + private static function getKey($keyOrKeyArray, $kid = null) { - if ( - is_string($keyOrKeyArray) - || is_resource($keyOrKeyArray) - || $keyOrKeyArray instanceof OpenSSLAsymmetricKey - ) { - return array($keyOrKeyArray, null); - } - if ($keyOrKeyArray instanceof Key) { - return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm()); + return $keyOrKeyArray; } if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) { + foreach ($keyOrKeyArray as $keyId => $key) { + if (!$key instanceof Key) { + throw new UnexpectedValueException( + '$keyOrKeyArray must be an instance of Firebase\JWT\Key key or an ' + . 'array of Firebase\JWT\Key keys' + ); + } + } if (!isset($kid)) { throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); } @@ -415,18 +401,12 @@ private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); } - $key = $keyOrKeyArray[$kid]; - - if ($key instanceof Key) { - return array($key->getKeyMaterial(), $key->getAlgorithm()); - } - - return array($key, null); + return $keyOrKeyArray[$kid]; } throw new UnexpectedValueException( - '$keyOrKeyArray must be a string|resource key, an array of string|resource keys, ' - . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys' + '$keyOrKeyArray must be an instance of Firebase\JWT\Key key or an ' + . 'array of Firebase\JWT\Key keys' ); } diff --git a/tests/JWKTest.php b/tests/JWKTest.php index 0709836d..fb94a34b 100644 --- a/tests/JWKTest.php +++ b/tests/JWKTest.php @@ -38,26 +38,42 @@ public function testParsePrivateKey() 'UnexpectedValueException', 'RSA private keys are not supported' ); - + $jwkSet = json_decode( file_get_contents(__DIR__ . '/rsa-jwkset.json'), true ); $jwkSet['keys'][0]['d'] = 'privatekeyvalue'; - + JWK::parseKeySet($jwkSet); } - + + public function testParsePrivateKeyWithoutAlg() + { + $this->setExpectedException( + 'InvalidArgumentException', + 'JWK key is missing "alg"' + ); + + $jwkSet = json_decode( + file_get_contents(__DIR__ . '/rsa-jwkset.json'), + true + ); + unset($jwkSet['keys'][0]['alg']); + + JWK::parseKeySet($jwkSet); + } + public function testParseKeyWithEmptyDValue() { $jwkSet = json_decode( file_get_contents(__DIR__ . '/rsa-jwkset.json'), true ); - + // empty or null values are ok $jwkSet['keys'][0]['d'] = null; - + $keys = JWK::parseKeySet($jwkSet); $this->assertTrue(is_array($keys)); } diff --git a/tests/JWTTest.php b/tests/JWTTest.php index 1c81c1ed..593ba7c6 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -23,7 +23,7 @@ public function testDecodeFromPython() { $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; $this->assertEquals( - JWT::decode($msg, 'my_key', array('HS256')), + JWT::decode($msg, new Key('my_key', 'HS256')), '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' ); } @@ -31,7 +31,7 @@ public function testDecodeFromPython() public function testUrlSafeCharacters() { $encoded = JWT::encode('f?', 'a'); - $this->assertEquals('f?', JWT::decode($encoded, 'a', array('HS256'))); + $this->assertEquals('f?', JWT::decode($encoded, new Key('a', 'HS256'))); } public function testMalformedUtf8StringsFail() @@ -53,7 +53,7 @@ public function testExpiredToken() "message" => "abc", "exp" => time() - 20); // time in the past $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + JWT::decode($encoded, new Key('my_key', 'HS256')); } public function testBeforeValidTokenWithNbf() @@ -63,7 +63,7 @@ public function testBeforeValidTokenWithNbf() "message" => "abc", "nbf" => time() + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + JWT::decode($encoded, new Key('my_key', 'HS256')); } public function testBeforeValidTokenWithIat() @@ -73,7 +73,7 @@ public function testBeforeValidTokenWithIat() "message" => "abc", "iat" => time() + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + JWT::decode($encoded, new Key('my_key', 'HS256')); } public function testValidToken() @@ -82,7 +82,7 @@ public function testValidToken() "message" => "abc", "exp" => time() + JWT::$leeway + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); } @@ -93,7 +93,7 @@ public function testValidTokenWithLeeway() "message" => "abc", "exp" => time() - 20); // time in the past $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); JWT::$leeway = 0; } @@ -106,21 +106,11 @@ public function testExpiredTokenWithLeeway() "exp" => time() - 70); // time far in the past $this->setExpectedException('Firebase\JWT\ExpiredException'); $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); JWT::$leeway = 0; } - public function testValidTokenWithList() - { - $payload = array( - "message" => "abc", - "exp" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256', 'HS512')); - $this->assertEquals($decoded->message, 'abc'); - } - public function testValidTokenWithNbf() { $payload = array( @@ -129,7 +119,7 @@ public function testValidTokenWithNbf() "exp" => time() + 20, // time in the future "nbf" => time() - 20); $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); } @@ -140,7 +130,7 @@ public function testValidTokenWithNbfLeeway() "message" => "abc", "nbf" => time() + 20); // not before in near (leeway) future $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); JWT::$leeway = 0; } @@ -153,7 +143,7 @@ public function testInvalidTokenWithNbfLeeway() "nbf" => time() + 65); // not before too far in future $encoded = JWT::encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\BeforeValidException'); - JWT::decode($encoded, 'my_key', array('HS256')); + JWT::decode($encoded, new Key('my_key', 'HS256')); JWT::$leeway = 0; } @@ -164,7 +154,7 @@ public function testValidTokenWithIatLeeway() "message" => "abc", "iat" => time() + 20); // issued in near (leeway) future $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); $this->assertEquals($decoded->message, 'abc'); JWT::$leeway = 0; } @@ -177,7 +167,7 @@ public function testInvalidTokenWithIatLeeway() "iat" => time() + 65); // issued too far in future $encoded = JWT::encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\BeforeValidException'); - JWT::decode($encoded, 'my_key', array('HS256')); + JWT::decode($encoded, new Key('my_key', 'HS256')); JWT::$leeway = 0; } @@ -188,7 +178,7 @@ public function testInvalidToken() "exp" => time() + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\SignatureInvalidException'); - JWT::decode($encoded, 'my_key2', array('HS256')); + JWT::decode($encoded, new Key('my_key2', 'HS256')); } public function testNullKeyFails() @@ -198,7 +188,7 @@ public function testNullKeyFails() "exp" => time() + JWT::$leeway + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); $this->setExpectedException('InvalidArgumentException'); - JWT::decode($encoded, null, array('HS256')); + JWT::decode($encoded, new Key(null, 'HS256')); } public function testEmptyKeyFails() @@ -208,22 +198,28 @@ public function testEmptyKeyFails() "exp" => time() + JWT::$leeway + 20); // time in the future $encoded = JWT::encode($payload, 'my_key'); $this->setExpectedException('InvalidArgumentException'); - JWT::decode($encoded, '', array('HS256')); + JWT::decode($encoded, new Key('', 'HS256')); } public function testKIDChooser() { - $keys = array('1' => 'my_key', '2' => 'my_key2'); - $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); - $decoded = JWT::decode($msg, $keys, array('HS256')); + $keys = array( + '1' => new Key('my_key', 'HS256'), + '2' => new Key('my_key2', 'HS256') + ); + $msg = JWT::encode('abc', $keys['1']->getKeyMaterial(), 'HS256', '1'); + $decoded = JWT::decode($msg, $keys); $this->assertEquals($decoded, 'abc'); } public function testArrayAccessKIDChooser() { - $keys = new ArrayObject(array('1' => 'my_key', '2' => 'my_key2')); - $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); - $decoded = JWT::decode($msg, $keys, array('HS256')); + $keys = new ArrayObject(array( + '1' => new Key('my_key', 'HS256'), + '2' => new Key('my_key2', 'HS256'), + )); + $msg = JWT::encode('abc', $keys['1']->getKeyMaterial(), 'HS256', '1'); + $decoded = JWT::decode($msg, $keys); $this->assertEquals($decoded, 'abc'); } @@ -231,46 +227,46 @@ public function testNoneAlgorithm() { $msg = JWT::encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key', array('none')); + JWT::decode($msg, new Key('my_key', 'none')); } public function testIncorrectAlgorithm() { $msg = JWT::encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key', array('RS256')); + JWT::decode($msg, new Key('my_key', 'RS256')); } - public function testMissingAlgorithm() + public function testEmptyAlgorithm() { $msg = JWT::encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key'); + JWT::decode($msg, new Key('my_key', '')); } public function testAdditionalHeaders() { $msg = JWT::encode('abc', 'my_key', 'HS256', null, array('cty' => 'test-eit;v=1')); - $this->assertEquals(JWT::decode($msg, 'my_key', array('HS256')), 'abc'); + $this->assertEquals(JWT::decode($msg, new Key('my_key', 'HS256')), 'abc'); } public function testInvalidSegmentCount() { $this->setExpectedException('UnexpectedValueException'); - JWT::decode('brokenheader.brokenbody', 'my_key', array('HS256')); + JWT::decode('brokenheader.brokenbody', new Key('my_key', 'HS256')); } public function testInvalidSignatureEncoding() { $msg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImZvbyJ9.Q4Kee9E8o0Xfo4ADXvYA8t7dN_X_bU9K5w6tXuiSjlUxx"; $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'secret', array('HS256')); + JWT::decode($msg, new Key('secret', 'HS256')); } public function testHSEncodeDecode() { $msg = JWT::encode('abc', 'my_key'); - $this->assertEquals(JWT::decode($msg, 'my_key', array('HS256')), 'abc'); + $this->assertEquals(JWT::decode($msg, new Key('my_key', 'HS256')), 'abc'); } public function testRSEncodeDecode() @@ -281,7 +277,7 @@ public function testRSEncodeDecode() $msg = JWT::encode('abc', $privKey, 'RS256'); $pubKey = openssl_pkey_get_details($privKey); $pubKey = $pubKey['key']; - $decoded = JWT::decode($msg, $pubKey, array('RS256')); + $decoded = JWT::decode($msg, new Key($pubKey, 'RS256')); $this->assertEquals($decoded, 'abc'); } @@ -294,7 +290,7 @@ public function testEdDsaEncodeDecode() $msg = JWT::encode($payload, $privKey, 'EdDSA'); $pubKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); - $decoded = JWT::decode($msg, $pubKey, array('EdDSA')); + $decoded = JWT::decode($msg, new Key($pubKey, 'EdDSA')); $this->assertEquals('bar', $decoded->foo); } @@ -310,7 +306,7 @@ public function testInvalidEdDsaEncodeDecode() $keyPair = sodium_crypto_sign_keypair(); $pubKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); $this->setExpectedException('Firebase\JWT\SignatureInvalidException'); - JWT::decode($msg, $pubKey, array('EdDSA')); + JWT::decode($msg, new Key($pubKey, 'EdDSA')); } public function testRSEncodeDecodeWithPassphrase() @@ -323,7 +319,7 @@ public function testRSEncodeDecodeWithPassphrase() $jwt = JWT::encode('abc', $privateKey, 'RS256'); $keyDetails = openssl_pkey_get_details($privateKey); $pubKey = $keyDetails['key']; - $decoded = JWT::decode($jwt, $pubKey, array('RS256')); + $decoded = JWT::decode($jwt, new Key($pubKey, 'RS256')); $this->assertEquals($decoded, 'abc'); } @@ -337,23 +333,6 @@ public function testEncodeDecode($privateKeyFile, $publicKeyFile, $alg) $payload = array('foo' => 'bar'); $encoded = JWT::encode($payload, $privateKey, $alg); - // Verify decoding succeeds - $publicKey = file_get_contents($publicKeyFile); - $decoded = JWT::decode($encoded, $publicKey, array($alg)); - - $this->assertEquals('bar', $decoded->foo); - } - - /** - * @runInSeparateProcess - * @dataProvider provideEncodeDecode - */ - public function testEncodeDecodeWithKeyObject($privateKeyFile, $publicKeyFile, $alg) - { - $privateKey = file_get_contents($privateKeyFile); - $payload = array('foo' => 'bar'); - $encoded = JWT::encode($payload, $privateKey, $alg); - // Verify decoding succeeds $publicKey = file_get_contents($publicKeyFile); $decoded = JWT::decode($encoded, new Key($publicKey, $alg)); @@ -361,17 +340,6 @@ public function testEncodeDecodeWithKeyObject($privateKeyFile, $publicKeyFile, $ $this->assertEquals('bar', $decoded->foo); } - public function testArrayAccessKIDChooserWithKeyObject() - { - $keys = new ArrayObject(array( - '1' => new Key('my_key', 'HS256'), - '2' => new Key('my_key2', 'HS256'), - )); - $msg = JWT::encode('abc', $keys['1']->getKeyMaterial(), 'HS256', '1'); - $decoded = JWT::decode($msg, $keys); - $this->assertEquals($decoded, 'abc'); - } - public function provideEncodeDecode() { return array( @@ -392,7 +360,7 @@ public function testEncodeDecodeWithResource() $encoded = JWT::encode($payload, $privateKey, 'RS512'); // Verify decoding succeeds - $decoded = JWT::decode($encoded, $resource, array('RS512')); + $decoded = JWT::decode($encoded, new Key($resource, 'RS512')); $this->assertEquals('bar', $decoded->foo); } diff --git a/tests/rsa-jwkset.json b/tests/rsa-jwkset.json index 0059f8cc..d5abfca9 100644 --- a/tests/rsa-jwkset.json +++ b/tests/rsa-jwkset.json @@ -4,14 +4,15 @@ "kty": "RSA", "e": "AQAB", "kid": "jwk1", - "n": "0Ttga33B1yX4w77NbpKyNYDNSVCo8j-RlZaZ9tI-KfkV1d-tfsvI9ZPAheP11FoN52ceBaY5ltelHW-IKwCfyT0orLdsxLgowaXki9woF1Azvcg2JVxQLv9aVjjAvy3CZFIG_EeN7J3nsyCXGnu1yMEbnvkWxA88__Q6HQ2K9wqfApkQ0LNlsK0YHz_sfjHNvRKxnbAJk7D5fUhZunPZXOPHXFgA5SvLvMaNIXduMKJh4OMfuoLdJowXJAR9j31Mqz_is4FMhm_9Mq7vZZ-uF09htRvIR8tRY28oJuW1gKWyg7cQQpnjHgFyG3XLXWAeXclWqyh_LfjyHQjrYhyeFw" - + "n": "0Ttga33B1yX4w77NbpKyNYDNSVCo8j-RlZaZ9tI-KfkV1d-tfsvI9ZPAheP11FoN52ceBaY5ltelHW-IKwCfyT0orLdsxLgowaXki9woF1Azvcg2JVxQLv9aVjjAvy3CZFIG_EeN7J3nsyCXGnu1yMEbnvkWxA88__Q6HQ2K9wqfApkQ0LNlsK0YHz_sfjHNvRKxnbAJk7D5fUhZunPZXOPHXFgA5SvLvMaNIXduMKJh4OMfuoLdJowXJAR9j31Mqz_is4FMhm_9Mq7vZZ-uF09htRvIR8tRY28oJuW1gKWyg7cQQpnjHgFyG3XLXWAeXclWqyh_LfjyHQjrYhyeFw", + "alg": "RS256" }, { "kty": "RSA", "e": "AQAB", "kid": "jwk2", - "n": "pXi2o6AnNhwL30MaK_nuDHi2fxZHVen7Xwk0bjLGlHYpq3mSvXm2HBA-zR41vQCbHkYGsDpsyDhIXLBDTbSa7ue7D1ZqYdv5YLIS33zdX9GtUHfFHc6zYgXAU9ziWeyTzVn7icAbjxqcgT2xKNuGK7Zf2ZJ053rr-dxjAE-SjX4SG0WWUhwPjxlr1etF7mEurhHweuSdZYl36g39o9BtTBVfS87io2MwdIRsnL3w8ulgXRVRWjv-vvcuhMS_y6zGbzOC55Yr23sb4h2PSll32bgyglEIsGgHqjOdyjuUzl0t6jh86DHzbu9h-u1iihX8EI8t7CBbizbPPyHQygp-rQ" + "n": "pXi2o6AnNhwL30MaK_nuDHi2fxZHVen7Xwk0bjLGlHYpq3mSvXm2HBA-zR41vQCbHkYGsDpsyDhIXLBDTbSa7ue7D1ZqYdv5YLIS33zdX9GtUHfFHc6zYgXAU9ziWeyTzVn7icAbjxqcgT2xKNuGK7Zf2ZJ053rr-dxjAE-SjX4SG0WWUhwPjxlr1etF7mEurhHweuSdZYl36g39o9BtTBVfS87io2MwdIRsnL3w8ulgXRVRWjv-vvcuhMS_y6zGbzOC55Yr23sb4h2PSll32bgyglEIsGgHqjOdyjuUzl0t6jh86DHzbu9h-u1iihX8EI8t7CBbizbPPyHQygp-rQ", + "alg": "RS256" } ] } \ No newline at end of file