├── JWT.php ├── LICENSE ├── Makefile ├── package.php ├── package.xml └── tests ├── Bootstrap.php ├── JWTTest.php └── phpunit.xml /JWT.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class JWT 12 | { 13 | /** 14 | * @param string $jwt The JWT 15 | * @param string|null $key The secret key 16 | * @param bool $verify Don't skip verification process 17 | * 18 | * @return object The JWT's payload as a PHP object 19 | */ 20 | public static function decode($jwt, $key = null, $verify = true) 21 | { 22 | $tks = explode('.', $jwt); 23 | if (count($tks) != 3) { 24 | throw new UnexpectedValueException('Wrong number of segments'); 25 | } 26 | list($headb64, $payloadb64, $cryptob64) = $tks; 27 | if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64))) 28 | ) { 29 | throw new UnexpectedValueException('Invalid segment encoding'); 30 | } 31 | if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($payloadb64)) 32 | ) { 33 | throw new UnexpectedValueException('Invalid segment encoding'); 34 | } 35 | $sig = JWT::urlsafeB64Decode($cryptob64); 36 | if ($verify) { 37 | if (empty($header->alg)) { 38 | throw new DomainException('Empty algorithm'); 39 | } 40 | if ($sig != JWT::sign("$headb64.$payloadb64", $key, $header->alg)) { 41 | throw new UnexpectedValueException('Signature verification failed'); 42 | } 43 | } 44 | return $payload; 45 | } 46 | 47 | /** 48 | * @param object|array $payload PHP object or array 49 | * @param string $key The secret key 50 | * @param string $algo The signing algorithm 51 | * 52 | * @return string A JWT 53 | */ 54 | public static function encode($payload, $key, $algo = 'HS256') 55 | { 56 | $header = array('typ' => 'JWT', 'alg' => $algo); 57 | 58 | $segments = array(); 59 | $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); 60 | $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); 61 | $signing_input = implode('.', $segments); 62 | 63 | $signature = JWT::sign($signing_input, $key, $algo); 64 | $segments[] = JWT::urlsafeB64Encode($signature); 65 | 66 | return implode('.', $segments); 67 | } 68 | 69 | /** 70 | * @param string $msg The message to sign 71 | * @param string $key The secret key 72 | * @param string $method The signing algorithm 73 | * 74 | * @return string An encrypted message 75 | */ 76 | public static function sign($msg, $key, $method = 'HS256') 77 | { 78 | $methods = array( 79 | 'HS256' => 'sha256', 80 | 'HS384' => 'sha384', 81 | 'HS512' => 'sha512', 82 | ); 83 | if (empty($methods[$method])) { 84 | throw new DomainException('Algorithm not supported'); 85 | } 86 | return hash_hmac($methods[$method], $msg, $key, true); 87 | } 88 | 89 | /** 90 | * @param string $input JSON string 91 | * 92 | * @return object Object representation of JSON string 93 | */ 94 | public static function jsonDecode($input) 95 | { 96 | $obj = json_decode($input); 97 | if (function_exists('json_last_error') && $errno = json_last_error()) { 98 | JWT::handleJsonError($errno); 99 | } 100 | else if ($obj === null && $input !== 'null') { 101 | throw new DomainException('Null result with non-null input'); 102 | } 103 | return $obj; 104 | } 105 | 106 | /** 107 | * @param object|array $input A PHP object or array 108 | * 109 | * @return string JSON representation of the PHP object or array 110 | */ 111 | public static function jsonEncode($input) 112 | { 113 | $json = json_encode($input); 114 | if (function_exists('json_last_error') && $errno = json_last_error()) { 115 | JWT::handleJsonError($errno); 116 | } 117 | else if ($json === 'null' && $input !== null) { 118 | throw new DomainException('Null result with non-null input'); 119 | } 120 | return $json; 121 | } 122 | 123 | /** 124 | * @param string $input A base64 encoded string 125 | * 126 | * @return string A decoded string 127 | */ 128 | public static function urlsafeB64Decode($input) 129 | { 130 | $remainder = strlen($input) % 4; 131 | if ($remainder) { 132 | $padlen = 4 - $remainder; 133 | $input .= str_repeat('=', $padlen); 134 | } 135 | return base64_decode(strtr($input, '-_', '+/')); 136 | } 137 | 138 | /** 139 | * @param string $input Anything really 140 | * 141 | * @return string The base64 encode of what you passed in 142 | */ 143 | public static function urlsafeB64Encode($input) 144 | { 145 | return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); 146 | } 147 | 148 | /** 149 | * @param int $errno An error number from json_last_error() 150 | * 151 | * @return void 152 | */ 153 | private static function handleJsonError($errno) 154 | { 155 | $messages = array( 156 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', 157 | JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', 158 | JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' 159 | ); 160 | throw new DomainException(isset($messages[$errno]) 161 | ? $messages[$errno] 162 | : 'Unknown JSON error: ' . $errno 163 | ); 164 | } 165 | 166 | } 167 | 168 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Neuman Vong 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Neuman Vong nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | test: 3 | @echo running tests 4 | @phpunit --configuration tests/phpunit.xml 5 | 6 | .PHONY: all test 7 | -------------------------------------------------------------------------------- /package.php: -------------------------------------------------------------------------------- 1 | setOptions( 20 | array( 21 | 'filelistgenerator' => 'file', 22 | 'simpleoutput' => true, 23 | 'baseinstalldir' => '/', 24 | 'packagedirectory' => './', 25 | 'dir_roles' => array( 26 | 'tests' => 'test' 27 | ), 28 | 'ignore' => array( 29 | 'package.php', 30 | '*.tgz' 31 | ) 32 | ) 33 | ); 34 | 35 | $package->setPackage('JWT'); 36 | $package->setSummary('A JWT encoder/decoder.'); 37 | $package->setDescription($description); 38 | $package->setChannel('pear.php.net'); 39 | $package->setPackageType('php'); 40 | $package->setLicense( 41 | 'MIT License', 42 | 'http://creativecommons.org/licenses/MIT/' 43 | ); 44 | 45 | $package->setNotes($release_notes); 46 | $package->setReleaseVersion($release_version); 47 | $package->setReleaseStability($release_state); 48 | $package->setAPIVersion($api_version); 49 | $package->setAPIStability($api_state); 50 | 51 | $package->addMaintainer( 52 | 'lead', 53 | 'lcfrs', 54 | 'Neuman Vong', 55 | 'neuman+pear@twilio.com' 56 | ); 57 | 58 | $package->addExtensionDep('required', 'json'); 59 | $package->addExtensionDep('required', 'hash'); 60 | 61 | $package->setPhpDep('5.1'); 62 | 63 | $package->setPearInstallerDep('1.7.0'); 64 | $package->generateContents(); 65 | $package->addRelease(); 66 | 67 | if ( isset($_GET['make']) 68 | || (isset($_SERVER['argv']) && @$_SERVER['argv'][1] == 'make') 69 | ) { 70 | $package->writePackageFile(); 71 | } else { 72 | $package->debugPackageFile(); 73 | } 74 | 75 | ?> 76 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | JWT 7 | pear.php.net 8 | A JWT encoder/decoder. 9 | A JWT encoder/decoder. 10 | 11 | Neuman Vong 12 | lcfrs 13 | neuman+pear@twilio.com 14 | yes 15 | 16 | 2011-03-25 17 | 18 | 19 | 0.0.0 20 | 0.0.0 21 | 22 | 23 | alpha 24 | alpha 25 | 26 | MIT License 27 | 28 | No release notes. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 5.1 45 | 46 | 47 | 1.7.0 48 | 49 | 50 | json 51 | 52 | 53 | hash 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0.0.0 62 | 0.0.0 63 | 64 | 65 | alpha 66 | alpha 67 | 68 | 2011-03-25 69 | MIT License 70 | 71 | No release notes. 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /tests/Bootstrap.php: -------------------------------------------------------------------------------- 1 | assertEquals(JWT::decode($msg, 'my_key'), 'abc'); 7 | } 8 | 9 | function testDecodeFromPython() { 10 | $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; 11 | $this->assertEquals( 12 | JWT::decode($msg, 'my_key'), 13 | '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' 14 | ); 15 | } 16 | 17 | function testUrlSafeCharacters() { 18 | $encoded = JWT::encode('f?', 'a'); 19 | $this->assertEquals('f?', JWT::decode($encoded, 'a')); 20 | } 21 | 22 | function testMalformedUtf8StringsFail() { 23 | $this->setExpectedException('DomainException'); 24 | JWT::encode(pack('c', 128), 'a'); 25 | } 26 | 27 | function testMalformedJsonThrowsException() { 28 | $this->setExpectedException('DomainException'); 29 | JWT::jsonDecode('this is not valid JSON string'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./ 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------