├── README.md
├── app_client.php
├── index.html
└── jwt.php
/README.md:
--------------------------------------------------------------------------------
1 | # jwtphpjquery
2 | A simple example that implements JSON Web Tokens using PHP and JQuery.
3 |
4 | Visit my blog article on Medium for an explanation of these sample files.
5 | https://medium.com/@crmcmullen/simple-example-using-json-web-tokens-with-php-and-jquery-c648a80854c
6 |
--------------------------------------------------------------------------------
/app_client.php:
--------------------------------------------------------------------------------
1 | $token);
54 | $jsonEncodedReturnArray = json_encode($returnArray, JSON_PRETTY_PRINT);
55 | echo $jsonEncodedReturnArray;
56 |
57 | }
58 | else {
59 | $returnArray = array('error' => 'Invalid user ID or password.');
60 | $jsonEncodedReturnArray = json_encode($returnArray, JSON_PRETTY_PRINT);
61 | echo $jsonEncodedReturnArray;
62 | }
63 |
64 | break;
65 |
66 | case 'GET':
67 |
68 | $token = null;
69 |
70 | if (isset($_GET['token'])) {$token = $_GET['token'];}
71 |
72 | if (!is_null($token)) {
73 |
74 | require_once('jwt.php');
75 |
76 | // Get our server-side secret key from a secure location.
77 | $serverKey = '5f2b5cdbe5194f10b3241568fe4e2b24';
78 |
79 | try {
80 | $payload = JWT::decode($token, $serverKey, array('HS256'));
81 | $returnArray = array('userId' => $payload->userId);
82 | if (isset($payload->exp)) {
83 | $returnArray['exp'] = date(DateTime::ISO8601, $payload->exp);;
84 | }
85 | }
86 | catch(Exception $e) {
87 | $returnArray = array('error' => $e->getMessage());
88 | }
89 | }
90 | else {
91 | $returnArray = array('error' => 'You are not logged in with a valid token.');
92 | }
93 |
94 | // return to caller
95 | $jsonEncodedReturnArray = json_encode($returnArray, JSON_PRETTY_PRINT);
96 | echo $jsonEncodedReturnArray;
97 |
98 | break;
99 |
100 | default:
101 | $returnArray = array('error' => 'You have requested an invalid method.');
102 | $jsonEncodedReturnArray = json_encode($returnArray, JSON_PRETTY_PRINT);
103 | echo $jsonEncodedReturnArray;
104 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JWT Example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/jwt.php:
--------------------------------------------------------------------------------
1 | array('hash_hmac', 'SHA256'),
19 | 'HS512' => array('hash_hmac', 'SHA512'),
20 | 'HS384' => array('hash_hmac', 'SHA384'),
21 | 'RS256' => array('openssl', 'SHA256'),
22 | 'RS384' => array('openssl', 'SHA384'),
23 | 'RS512' => array('openssl', 'SHA512'),
24 | );
25 | /** ----------------------------------------------------------------------------------------------------------
26 | * Decodes a JWT string into a PHP object.
27 | *
28 | * @param string $token The JSON web token
29 | * @param string|array $key The secret key
30 | * @param array $allowed_algs If the algorithm used is asymmetric, this is the public key list
31 | * of supported verification algorithms. Supported algorithms are:
32 | * 'HS256', 'HS384', 'HS512' and 'RS256'
33 | *
34 | * @return object The JWT's payload as a PHP object
35 | *
36 | */
37 | public static function decode($token, $key, array $allowed_algs = array())
38 | {
39 | if ((!isset($timestamp)) || (is_null($timestamp))) {
40 | $timestamp = time();
41 | }
42 |
43 | if (empty($key)) {
44 | throw new Exception('Invalid or missing key.');
45 | }
46 |
47 | $tokenSegments = explode('.', $token);
48 |
49 | if (count($tokenSegments) != 3) {
50 | throw new Exception('Wrong number of segments');
51 | }
52 |
53 | list($headb64, $bodyb64, $cryptob64) = $tokenSegments;
54 | if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
55 | throw new Exception('Invalid header encoding');
56 | }
57 |
58 | if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
59 | throw new Exception('Invalid claims encoding');
60 | }
61 |
62 | if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
63 | throw new Exception('Invalid signature encoding');
64 | }
65 |
66 | if (empty($header->alg)) {
67 | throw new Exception('Empty algorithm');
68 | }
69 |
70 | if (empty(static::$supported_algs[$header->alg])) {
71 | throw new Exception('Algorithm not supported');
72 | }
73 |
74 | if (!in_array($header->alg, $allowed_algs)) {
75 | throw new Exception('Algorithm not allowed');
76 | }
77 |
78 | if (is_array($key) || $key instanceof ArrayAccess) {
79 | if (isset($header->kid)) {
80 | if (!isset($key[$header->kid])) {
81 | throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
82 | }
83 | $key = $key[$header->kid];
84 | } else {
85 | throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
86 | }
87 | }
88 |
89 | // Check the signature
90 | if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
91 | throw new Exception('Signature verification failed');
92 | }
93 |
94 | // Check if the nbf if it is defined. This is the time that the
95 | // token can actually be used. If it's not yet that time, abort.
96 | if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
97 | throw new Exception(
98 | 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
99 | );
100 | }
101 |
102 | // Check that this token has been created before 'now'. This prevents
103 | // using tokens that have been created for later use (and haven't
104 | // correctly used the nbf claim).
105 | if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
106 | throw new Exception(
107 | 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
108 | );
109 | }
110 |
111 | // Check if this token has expired.
112 | if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
113 | throw new Exception('Expired token');
114 | }
115 |
116 | return $payload;
117 | }
118 | /** ----------------------------------------------------------------------------------------------------------
119 | * Converts and signs a PHP object or array into a JWT string.
120 | *
121 | * @param object|array $payload PHP object or array
122 | * @param string $key The secret key.
123 | * If the algorithm used is asymmetric, this is the private key
124 | * @param string $alg The signing algorithm.
125 | * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
126 | * @param mixed $keyId
127 | * @param array $head An array with header elements to attach
128 | *
129 | * @return string A signed JWT
130 | *
131 | */
132 | public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
133 | {
134 | $header = array('typ' => 'JWT', 'alg' => $alg);
135 |
136 | if ($keyId !== null) {
137 | $header['kid'] = $keyId;
138 | }
139 |
140 | if ( isset($head) && is_array($head) ) {
141 | $header = array_merge($head, $header);
142 | }
143 |
144 | $segments = array();
145 | $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
146 | $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
147 | $signing_input = implode('.', $segments);
148 | $signature = static::sign($signing_input, $key, $alg);
149 | $segments[] = static::urlsafeB64Encode($signature);
150 |
151 | return implode('.', $segments);
152 | }
153 | /** ----------------------------------------------------------------------------------------------------------
154 | * Sign a string with a given key and algorithm.
155 | *
156 | * @param string $msg The message to sign
157 | * @param string|resource $key The secret key
158 | * @param string $alg The signing algorithm.
159 | * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
160 | *
161 | * @return string An encrypted message
162 | *
163 | */
164 | public static function sign($msg, $key, $alg = 'HS256')
165 | {
166 | if (empty(static::$supported_algs[$alg])) {
167 | throw new Exception('Algorithm not supported');
168 | }
169 | list($function, $algorithm) = static::$supported_algs[$alg];
170 | switch($function) {
171 | case 'hash_hmac':
172 | return hash_hmac($algorithm, $msg, $key, true);
173 | case 'openssl':
174 | $signature = '';
175 | $success = openssl_sign($msg, $signature, $key, $algorithm);
176 | if (!$success) {
177 | throw new Exception("OpenSSL unable to sign data");
178 | } else {
179 | return $signature;
180 | }
181 | }
182 | }
183 | /** ----------------------------------------------------------------------------------------------------------
184 | * Verify a signature with the message, key and method. Not all methods
185 | * are symmetric, so we must have a separate verify and sign method.
186 | *
187 | * @param string $msg The original message (header and body)
188 | * @param string $signature The original signature
189 | * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
190 | * @param string $alg The algorithm
191 | *
192 | * @return bool
193 | */
194 | private static function verify($msg, $signature, $key, $alg)
195 | {
196 | if (empty(static::$supported_algs[$alg])) {
197 | throw new Exception('Algorithm not supported');
198 | }
199 | list($function, $algorithm) = static::$supported_algs[$alg];
200 | switch($function) {
201 | case 'openssl':
202 | $success = openssl_verify($msg, $signature, $key, $algorithm);
203 | if ($success === 1) {
204 | return true;
205 | } elseif ($success === 0) {
206 | return false;
207 | }
208 | // returns 1 on success, 0 on failure, -1 on error.
209 | throw new Exception(
210 | 'OpenSSL error: ' . openssl_error_string()
211 | );
212 | case 'hash_hmac':
213 | default:
214 | $hash = hash_hmac($algorithm, $msg, $key, true);
215 | if (function_exists('hash_equals')) {
216 | return hash_equals($signature, $hash);
217 | }
218 | $len = min(static::safeStrlen($signature), static::safeStrlen($hash));
219 | $status = 0;
220 | for ($i = 0; $i < $len; $i++) {
221 | $status |= (ord($signature[$i]) ^ ord($hash[$i]));
222 | }
223 | $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
224 | return ($status === 0);
225 | }
226 | }
227 | /** ----------------------------------------------------------------------------------------------------------
228 | * Decode a JSON string into a PHP object.
229 | *
230 | * @param string $input JSON string
231 | *
232 | * @return object Object representation of JSON string
233 | *
234 | * @throws Exception Provided string was invalid JSON
235 | */
236 | public static function jsonDecode($input)
237 | {
238 | if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
239 | /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
240 | * to specify that large ints (like Steam Transaction IDs) should be treated as
241 | * strings, rather than the PHP default behaviour of converting them to floats.
242 | */
243 | $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
244 | } else {
245 | /** Not all servers will support that, however, so for older versions we must
246 | * manually detect large ints in the JSON string and quote them (thus converting
247 | *them to strings) before decoding, hence the preg_replace() call.
248 | */
249 | $max_int_length = strlen((string) PHP_INT_MAX) - 1;
250 | $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
251 | $obj = json_decode($json_without_bigints);
252 | }
253 | if (function_exists('json_last_error') && $errno = json_last_error()) {
254 | static::handleJsonError($errno);
255 | } elseif ($obj === null && $input !== 'null') {
256 | throw new Exception('Null result with non-null input');
257 | }
258 | return $obj;
259 | }
260 | /** ----------------------------------------------------------------------------------------------------------
261 | * Encode a PHP object into a JSON string.
262 | *
263 | * @param object|array $input A PHP object or array
264 | *
265 | * @return string JSON representation of the PHP object or array
266 | *
267 | * @throws Exception Provided object could not be encoded to valid JSON
268 | */
269 | public static function jsonEncode($input)
270 | {
271 | $json = json_encode($input);
272 | if (function_exists('json_last_error') && $errno = json_last_error()) {
273 | static::handleJsonError($errno);
274 | } elseif ($json === 'null' && $input !== null) {
275 | throw new Exception('Null result with non-null input');
276 | }
277 | return $json;
278 | }
279 | /** ----------------------------------------------------------------------------------------------------------
280 | * Decode a string with URL-safe Base64.
281 | *
282 | * @param string $input A Base64 encoded string
283 | *
284 | * @return string A decoded string
285 | */
286 | public static function urlsafeB64Decode($input)
287 | {
288 | $remainder = strlen($input) % 4;
289 | if ($remainder) {
290 | $padlen = 4 - $remainder;
291 | $input .= str_repeat('=', $padlen);
292 | }
293 | return base64_decode(strtr($input, '-_', '+/'));
294 | }
295 | /** ----------------------------------------------------------------------------------------------------------
296 | * Encode a string with URL-safe Base64.
297 | *
298 | * @param string $input The string you want encoded
299 | *
300 | * @return string The base64 encode of what you passed in
301 | */
302 | public static function urlsafeB64Encode($input)
303 | {
304 | return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
305 | }
306 | /** ----------------------------------------------------------------------------------------------------------
307 | * Helper method to create a JSON error.
308 | *
309 | * @param int $errno An error number from json_last_error()
310 | *
311 | * @return void
312 | */
313 | private static function handleJsonError($errno)
314 | {
315 | $messages = array(
316 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
317 | JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
318 | JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
319 | JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
320 | JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
321 | );
322 | throw new Exception(
323 | isset($messages[$errno])
324 | ? $messages[$errno]
325 | : 'Unknown JSON error: ' . $errno
326 | );
327 | }
328 | /** ----------------------------------------------------------------------------------------------------------
329 | * Get the number of bytes in cryptographic strings.
330 | *
331 | * @param string
332 | *
333 | * @return int
334 | */
335 | private static function safeStrlen($str)
336 | {
337 | if (function_exists('mb_strlen')) {
338 | return mb_strlen($str, '8bit');
339 | }
340 | return strlen($str);
341 | }
342 | }
--------------------------------------------------------------------------------