Skip to content

Commit b582985

Browse files
committed
1. Allow encode to take a "kid" parameter
2. Allow decode to take an array of keys, and use the "kid" to look up the key. 3. Add RS256 as an algorithm 4. Add tests for the above
1 parent 144e201 commit b582985

File tree

2 files changed

+41
-25
lines changed

2 files changed

+41
-25
lines changed

Authentication/JWT.php

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,12 @@ class JWT
3232
'HS384' => array('hash_hmac', 'SHA384'),
3333
'RS256' => array('openssl', 'SHA256'),
3434
);
35-
/**
36-
* Returns just the header portion of the jwt. This allows
37-
* you to determine which key should be used to verify
38-
* the jwt, using the "kid" field
39-
*
40-
* @param string $jwt
41-
*
42-
* @return object The JWT's header object, with fields "typ","alg", and optionally "kid"
43-
*/
44-
public static function decodeHeader($jwt) {
45-
$tks = explode('.', $jwt);
46-
if (count($tks) != 3) {
47-
throw new UnexpectedValueException('Wrong number of segments');
48-
}
49-
list($headb64, $bodyb64, $cryptob64) = $tks;
50-
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
51-
throw new UnexpectedValueException('Invalid segment encoding');
52-
}
53-
return $header;
54-
}
5535

5636
/**
5737
* Decodes a JWT string into a PHP object.
5838
*
5939
* @param string $jwt The JWT
60-
* @param string|null $key The secret key
40+
* @param string|Array|null $key The secret key, or map of keys
6141
* @param bool $verify Don't skip verification process
6242
*
6343
* @return object The JWT's payload as a PHP object
@@ -85,6 +65,11 @@ public static function decode($jwt, $key = null, $verify = true)
8565
if (empty($header->alg)) {
8666
throw new DomainException('Empty algorithm');
8767
}
68+
if (is_array($key) && !isset($header->kid)) {
69+
throw new DomainException('"kid" empty, unable to lookup correct key');
70+
} elseif(is_array($key) && isset($header->kid)) {
71+
$key = $key[$header->kid];
72+
}
8873
if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
8974
throw new UnexpectedValueException('Signature verification failed');
9075
}
@@ -108,10 +93,12 @@ public static function decode($jwt, $key = null, $verify = true)
10893
* @uses jsonEncode
10994
* @uses urlsafeB64Encode
11095
*/
111-
public static function encode($payload, $key, $algo = 'HS256')
96+
public static function encode($payload, $key, $algo = 'HS256', $keyId = null)
11297
{
11398
$header = array('typ' => 'JWT', 'alg' => $algo);
114-
99+
if($keyId !== null) {
100+
$header['kid'] = $keyId;
101+
}
115102
$segments = array();
116103
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
117104
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
@@ -127,7 +114,7 @@ public static function encode($payload, $key, $algo = 'HS256')
127114
* Sign a string with a given key and algorithm.
128115
*
129116
* @param string $msg The message to sign
130-
* @param string $key The secret key
117+
* @param string|resource $key The secret key
131118
* @param string $method The signing algorithm. Supported
132119
* algorithms are 'HS256', 'HS384' and 'HS512'
133120
*
@@ -154,6 +141,16 @@ public static function sign($msg, $key, $method = 'HS256')
154141
}
155142
}
156143

144+
/**
145+
* Verify a signature with the mesage, key and method. Not all methods
146+
* are symmetric, so we must have a separate verify and sign method.
147+
* @param string $msg the original message
148+
* @param string $signature
149+
* @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
150+
* @param string $method
151+
* @return bool
152+
* @throws DomainException Invalid Algorithm or OpenSSL failure
153+
*/
157154
public static function verify($msg, $signature, $key, $method = 'HS256') {
158155
if (empty(self::$methods[$method])) {
159156
throw new DomainException('Algorithm not supported');
@@ -163,7 +160,7 @@ public static function verify($msg, $signature, $key, $method = 'HS256') {
163160
case 'openssl':
164161
$success = openssl_verify($msg, $signature, $key, $algo);
165162
if(!$success) {
166-
throw new DomainException("OpenSSL unable to sign data");
163+
throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
167164
} else {
168165
return $signature;
169166
}

tests/JWTTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ function testValidToken(){
4646
$decoded = JWT::decode($encoded, 'my_key');
4747
$this->assertEquals($decoded->message, 'abc');
4848
}
49+
50+
function testRSEncodeDecode() {
51+
$privKey = openssl_pkey_new(array('digest_alg' => 'sha256',
52+
'private_key_bits' => 1024,
53+
'private_key_type' => OPENSSL_KEYTYPE_RSA));
54+
$msg = JWT::encode('abc',$privKey, 'RS256');
55+
$pubKey = openssl_pkey_get_details($privKey);
56+
$pubKey = $pubKey['key'];
57+
$decoded = JWT::decode($msg, $pubKey, true);
58+
$this->assertEquals($decoded, 'abc');
59+
}
60+
61+
function testKIDChooser() {
62+
$keys = array('1' => 'my_key', '2' => 'my_key2');
63+
$msg = JWT::encode('abc', $keys['1'], 'HS256', '1');
64+
$decoded = JWT::decode($msg, $keys, true);
65+
$this->assertEquals($decoded, 'abc');
66+
}
67+
4968

5069
}
5170

0 commit comments

Comments
 (0)