From 7cfab736b4499f8cd74f64d1ab1405b4e70466d3 Mon Sep 17 00:00:00 2001 From: Jason Walker Date: Thu, 21 Sep 2017 00:17:22 -0400 Subject: [PATCH] Static functions removed Updated code to use getInstance() instead of the none OOP way of making everything static Updated code to use PHP 7.1+ --- README.md | 4 +- html/index.php | 57 ++++++++++++++++++ src/JWT.php | 53 +++++++++-------- tests/JWTTest.php | 147 +++++++++++++++++++++++----------------------- 4 files changed, 164 insertions(+), 97 deletions(-) create mode 100644 html/index.php diff --git a/README.md b/README.md index b1a7a3a2..1e19b6b0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ Example ------- ```php "http://example.org/", + "aud" => "http://example.com/", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::getInstance()->encode($token, $key); +$decoded = JWT::getInstance()->decode($jwt, $key, array('HS256')); + +print_r($decoded); + +/* + 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; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::getInstance()->leeway = 60; // $leeway in seconds +$decoded = JWT::getInstance()->decode($jwt, $key, array('HS256')); + +print_r($decoded); + +$original = 'abc'; +$msg = JWT::getInstance()->encode($original, 'my_key'); +$decoded = JWT::getInstance()->decode($msg, 'my_key', array('HS256')); +print 'Original: '.$original."\n"; +print 'Message: '.$msg."\n"; +print 'Decoded: '.$decoded."\n\n"; + +$msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; +$decoded = JWT::getInstance()->decode($msg, 'my_key', array('HS256')); + +print "Original: *:http://application/clicky?blah=1.23&f.oo=456 AC000 123\n"; +print 'Message: '.$msg."\n"; +print 'Decoded: '.$decoded."\n\n"; +?> \ No newline at end of file diff --git a/src/JWT.php b/src/JWT.php index 22a67e32..7f5ad238 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -21,13 +21,13 @@ */ class JWT { - + private static $instance; /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to * account for clock skew. */ - public static $leeway = 0; + public $leeway = 0; /** * Allow the current timestamp to be specified. @@ -35,9 +35,9 @@ class JWT * * Will default to PHP time() value if null. */ - public static $timestamp = null; + public $timestamp = null; - public static $supported_algs = array( + public $supported_algs = array( 'HS256' => array('hash_hmac', 'SHA256'), 'HS512' => array('hash_hmac', 'SHA512'), 'HS384' => array('hash_hmac', 'SHA384'), @@ -46,6 +46,13 @@ class JWT 'RS512' => array('openssl', 'SHA512'), ); + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new JWT(); + } + return self::$instance; + } + /** * Decodes a JWT string into a PHP object. * @@ -66,9 +73,9 @@ class JWT * @uses jsonDecode * @uses urlsafeB64Decode */ - public static function decode($jwt, $key, array $allowed_algs = array()) + public function decode($jwt, $key, array $allowed_algs = array()) { - $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp; + $timestamp = is_null($this->timestamp) ? time() : $this->timestamp; if (empty($key)) { throw new InvalidArgumentException('Key may not be empty'); @@ -90,7 +97,7 @@ public static function decode($jwt, $key, array $allowed_algs = array()) if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); } - if (empty(static::$supported_algs[$header->alg])) { + if (empty($this->supported_algs[$header->alg])) { throw new UnexpectedValueException('Algorithm not supported'); } if (!in_array($header->alg, $allowed_algs)) { @@ -114,7 +121,7 @@ public static function decode($jwt, $key, array $allowed_algs = array()) // Check if the nbf if it is defined. This is the time that the // token can actually be used. If it's not yet that time, abort. - if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + if (isset($payload->nbf) && $payload->nbf > ($timestamp + $this->leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) ); @@ -123,14 +130,14 @@ public static function decode($jwt, $key, array $allowed_algs = array()) // Check that this token has been created before 'now'. This prevents // using tokens that have been created for later use (and haven't // correctly used the nbf claim). - if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + if (isset($payload->iat) && $payload->iat > ($timestamp + $this->leeway)) { throw new BeforeValidException( 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat) ); } // Check if this token has expired. - if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + if (isset($payload->exp) && ($timestamp - $this->leeway) >= $payload->exp) { throw new ExpiredException('Expired token'); } @@ -153,7 +160,7 @@ public static function decode($jwt, $key, array $allowed_algs = array()) * @uses jsonEncode * @uses urlsafeB64Encode */ - public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + public function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) { $header = array('typ' => 'JWT', 'alg' => $alg); if ($keyId !== null) { @@ -185,12 +192,12 @@ public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $he * * @throws DomainException Unsupported algorithm was specified */ - public static function sign($msg, $key, $alg = 'HS256') + public function sign($msg, $key, $alg = 'HS256') { - if (empty(static::$supported_algs[$alg])) { + if (empty($this->supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } - list($function, $algorithm) = static::$supported_algs[$alg]; + list($function, $algorithm) = $this->supported_algs[$alg]; switch($function) { case 'hash_hmac': return hash_hmac($algorithm, $msg, $key, true); @@ -218,13 +225,13 @@ public static function sign($msg, $key, $alg = 'HS256') * * @throws DomainException Invalid Algorithm or OpenSSL failure */ - private static function verify($msg, $signature, $key, $alg) + private function verify($msg, $signature, $key, $alg) { - if (empty(static::$supported_algs[$alg])) { + if (empty($this->supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } - list($function, $algorithm) = static::$supported_algs[$alg]; + list($function, $algorithm) = $this->supported_algs[$alg]; switch($function) { case 'openssl': $success = openssl_verify($msg, $signature, $key, $algorithm); @@ -264,7 +271,7 @@ private static function verify($msg, $signature, $key, $alg) * * @throws DomainException Provided string was invalid JSON */ - public static function jsonDecode($input) + public function jsonDecode($input) { if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you @@ -299,7 +306,7 @@ public static function jsonDecode($input) * * @throws DomainException Provided object could not be encoded to valid JSON */ - public static function jsonEncode($input) + public function jsonEncode($input) { $json = json_encode($input); if (function_exists('json_last_error') && $errno = json_last_error()) { @@ -317,7 +324,7 @@ public static function jsonEncode($input) * * @return string A decoded string */ - public static function urlsafeB64Decode($input) + public function urlsafeB64Decode($input) { $remainder = strlen($input) % 4; if ($remainder) { @@ -334,7 +341,7 @@ public static function urlsafeB64Decode($input) * * @return string The base64 encode of what you passed in */ - public static function urlsafeB64Encode($input) + public function urlsafeB64Encode($input) { return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); } @@ -346,7 +353,7 @@ public static function urlsafeB64Encode($input) * * @return void */ - private static function handleJsonError($errno) + private function handleJsonError($errno) { $messages = array( JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', @@ -369,7 +376,7 @@ private static function handleJsonError($errno) * * @return int */ - private static function safeStrlen($str) + private function safeStrlen($str) { if (function_exists('mb_strlen')) { return mb_strlen($str, '8bit'); diff --git a/tests/JWTTest.php b/tests/JWTTest.php index 804a3769..49502ac7 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -1,44 +1,45 @@ assertEquals(JWT::decode($msg, 'my_key', array('HS256')), 'abc'); + $msg = JWT::getInstance()->encode('abc', 'my_key'); + $this->assertEquals(JWT::getInstance()->decode($msg, 'my_key', array('HS256')), 'abc'); } public function testDecodeFromPython() { $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; $this->assertEquals( - JWT::decode($msg, 'my_key', array('HS256')), + JWT::getInstance()->decode($msg, 'my_key', array('HS256')), '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' ); } public function testUrlSafeCharacters() { - $encoded = JWT::encode('f?', 'a'); - $this->assertEquals('f?', JWT::decode($encoded, 'a', array('HS256'))); + $encoded = JWT::getInstance()->encode('f?', 'a'); + $this->assertEquals('f?', JWT::getInstance()->decode($encoded, 'a', array('HS256'))); } public function testMalformedUtf8StringsFail() { $this->setExpectedException('DomainException'); - JWT::encode(pack('c', 128), 'a'); + JWT::getInstance()->encode(pack('c', 128), 'a'); } public function testMalformedJsonThrowsException() { $this->setExpectedException('DomainException'); - JWT::jsonDecode('this is not valid JSON string'); + JWT::getInstance()->jsonDecode('this is not valid JSON string'); } public function testExpiredToken() @@ -47,8 +48,8 @@ public function testExpiredToken() $payload = array( "message" => "abc", "exp" => time() - 20); // time in the past - $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); } public function testBeforeValidTokenWithNbf() @@ -57,8 +58,8 @@ public function testBeforeValidTokenWithNbf() $payload = array( "message" => "abc", "nbf" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); } public function testBeforeValidTokenWithIat() @@ -67,43 +68,43 @@ public function testBeforeValidTokenWithIat() $payload = array( "message" => "abc", "iat" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); } public function testValidToken() { $payload = array( "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')); + "exp" => time() + JWT::getInstance()->leeway + 20); // time in the future + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); } public function testValidTokenWithLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "message" => "abc", "exp" => time() - 20); // time in the past - $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); - JWT::$leeway = 0; + JWT::getInstance()->leeway = 0; } public function testExpiredTokenWithLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "message" => "abc", "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')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); - JWT::$leeway = 0; + JWT::getInstance()->leeway = 0; } public function testValidTokenWithList() @@ -111,8 +112,8 @@ 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')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256', 'HS512')); $this->assertEquals($decoded->message, 'abc'); } @@ -123,57 +124,57 @@ public function testValidTokenWithNbf() "iat" => time(), "exp" => time() + 20, // time in the future "nbf" => time() - 20); - $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); } public function testValidTokenWithNbfLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "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')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); - JWT::$leeway = 0; + JWT::getInstance()->leeway = 0; } public function testInvalidTokenWithNbfLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "message" => "abc", "nbf" => time() + 65); // not before too far in future - $encoded = JWT::encode($payload, 'my_key'); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\BeforeValidException'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); - JWT::$leeway = 0; + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); + JWT::getInstance()->leeway = 0; } public function testValidTokenWithIatLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "message" => "abc", "iat" => time() + 20); // issued in near (leeway) future - $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); $this->assertEquals($decoded->message, 'abc'); - JWT::$leeway = 0; + JWT::getInstance()->leeway = 0; } public function testInvalidTokenWithIatLeeway() { - JWT::$leeway = 60; + JWT::getInstance()->leeway = 60; $payload = array( "message" => "abc", "iat" => time() + 65); // issued too far in future - $encoded = JWT::encode($payload, 'my_key'); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\BeforeValidException'); - $decoded = JWT::decode($encoded, 'my_key', array('HS256')); - JWT::$leeway = 0; + $decoded = JWT::getInstance()->decode($encoded, 'my_key', array('HS256')); + JWT::getInstance()->leeway = 0; } public function testInvalidToken() @@ -181,29 +182,29 @@ public function testInvalidToken() $payload = array( "message" => "abc", "exp" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); + $encoded = JWT::getInstance()->encode($payload, 'my_key'); $this->setExpectedException('Firebase\JWT\SignatureInvalidException'); - $decoded = JWT::decode($encoded, 'my_key2', array('HS256')); + $decoded = JWT::getInstance()->decode($encoded, 'my_key2', array('HS256')); } public function testNullKeyFails() { $payload = array( "message" => "abc", - "exp" => time() + JWT::$leeway + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); + "exp" => time() + JWT::getInstance()->leeway + 20); // time in the future + $encoded = JWT::getInstance()->encode($payload, 'my_key'); $this->setExpectedException('InvalidArgumentException'); - $decoded = JWT::decode($encoded, null, array('HS256')); + $decoded = JWT::getInstance()->decode($encoded, null, array('HS256')); } public function testEmptyKeyFails() { $payload = array( "message" => "abc", - "exp" => time() + JWT::$leeway + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); + "exp" => time() + JWT::getInstance()->leeway + 20); // time in the future + $encoded = JWT::getInstance()->encode($payload, 'my_key'); $this->setExpectedException('InvalidArgumentException'); - $decoded = JWT::decode($encoded, '', array('HS256')); + $decoded = JWT::getInstance()->decode($encoded, '', array('HS256')); } public function testRSEncodeDecode() @@ -211,76 +212,76 @@ public function testRSEncodeDecode() $privKey = openssl_pkey_new(array('digest_alg' => 'sha256', 'private_key_bits' => 1024, 'private_key_type' => OPENSSL_KEYTYPE_RSA)); - $msg = JWT::encode('abc', $privKey, 'RS256'); + $msg = JWT::getInstance()->encode('abc', $privKey, 'RS256'); $pubKey = openssl_pkey_get_details($privKey); $pubKey = $pubKey['key']; - $decoded = JWT::decode($msg, $pubKey, array('RS256')); + $decoded = JWT::getInstance()->decode($msg, $pubKey, array('RS256')); $this->assertEquals($decoded, 'abc'); } 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')); + $msg = JWT::getInstance()->encode('abc', $keys['1'], 'HS256', '1'); + $decoded = JWT::getInstance()->decode($msg, $keys, array('HS256')); $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')); + $msg = JWT::getInstance()->encode('abc', $keys['1'], 'HS256', '1'); + $decoded = JWT::getInstance()->decode($msg, $keys, array('HS256')); $this->assertEquals($decoded, 'abc'); } public function testNoneAlgorithm() { - $msg = JWT::encode('abc', 'my_key'); + $msg = JWT::getInstance()->encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key', array('none')); + JWT::getInstance()->decode($msg, 'my_key', array('none')); } public function testIncorrectAlgorithm() { - $msg = JWT::encode('abc', 'my_key'); + $msg = JWT::getInstance()->encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key', array('RS256')); + JWT::getInstance()->decode($msg, 'my_key', array('RS256')); } public function testMissingAlgorithm() { - $msg = JWT::encode('abc', 'my_key'); + $msg = JWT::getInstance()->encode('abc', 'my_key'); $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'my_key'); + JWT::getInstance()->decode($msg, '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'); + $msg = JWT::getInstance()->encode('abc', 'my_key', 'HS256', null, array('cty' => 'test-eit;v=1')); + $this->assertEquals(JWT::getInstance()->decode($msg, 'my_key', array('HS256')), 'abc'); } public function testInvalidSegmentCount() { $this->setExpectedException('UnexpectedValueException'); - JWT::decode('brokenheader.brokenbody', 'my_key', array('HS256')); + JWT::getInstance()->decode('brokenheader.brokenbody', 'my_key', array('HS256')); } public function testInvalidSignatureEncoding() { $msg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImZvbyJ9.Q4Kee9E8o0Xfo4ADXvYA8t7dN_X_bU9K5w6tXuiSjlUxx"; $this->setExpectedException('UnexpectedValueException'); - JWT::decode($msg, 'secret', array('HS256')); + JWT::getInstance()->decode($msg, 'secret', array('HS256')); } public function testVerifyError() { $this->setExpectedException('DomainException'); $pkey = openssl_pkey_new(); - $msg = JWT::encode('abc', $pkey, 'RS256'); + $msg = JWT::getInstance()->encode('abc', $pkey, 'RS256'); self::$opensslVerifyReturnValue = -1; - JWT::decode($msg, $pkey, array('RS256')); + JWT::getInstance()->decode($msg, $pkey, array('RS256')); } }