8wrxfw
Last Updated: April 13, 2017
·
70.55K
· rmcdaniel
11b2608696511fa5c3398aa9d6af4827

Goodbye PHP Sessions, Hello JSON Web Tokens

REST API's are meant to be stateless. What that means is that each request from a client should include all the information needed to process the request. In other words, if you are writing a REST API in PHP then you should not be using $_SESSION to store data about the client's session. But then how do we remember if a client is logged in or anything else about their state? The only possibility is that the client must be tasked with keeping track of the state. How could this ever be done securely? The client can't be trusted!

Enter JSON web tokens. A JSON web token is a bit of JSON, perhaps something that looks like this:

{
    "user": "alice",
    "email": "test@nospam.com"
}

Of course, we can't just give this to a client and have them give it back to us without some sort of assurance that it hasn't been tampered with. After all, what if they edit the token as follows:

{
    "user": "administrator",
    "email": "test@nospam.com"
}

The solution to this is that JSON web tokens are signed by the server. If the client tampers with the data then the token's signature will no longer match and an error can be raised.

The JWT PHP class makes this easy to do. For example, to create a token after the client successfully logs in, the following code could be used:

$token = array();
$token['id'] = $id;
echo JWT::encode($token, 'secret_server_key');

And then on later API calls the token can be retrieved and verified by this code:

$token = JWT::decode($_POST['token'], 'secret_server_key');
echo $token->id;

If the token has been tampered with then $token will be empty there will not be an id available. The JWT class makes sure that invalid data is never made available. If the token is tampered with, it will be unusable. Pretty simple stuff!

You can get the PHP JWT class as a single file from: https://github.com/rmcdaniel/angular-codeigniter-seed/blob/master/api/application/helpers/jwt_helper.php

as it is used by the AngularJS CodeIgniter Seed project:

https://github.com/rmcdaniel/angular-codeigniter-seed

or the original code from:

https://github.com/luciferous/jwt

13 Responses
Add your response

15276
0 nfyu88a3zrepz iqnlp18g4lvgytn tqnhe18gxteze wf5n43u8aci7u1puckp4vxyt753d der

You would still have to compare the $POST['token'] with one on the server. $SESSION could hold this but it's only really good on small projects that dont require load balanced servers. A better solution would be to have a backend cache server like memcache which stores the user_id and any other useful data under a random key which you assign to the token to pass back to the user. The user then posts the key back and the data is fetched from the cache referenced by the key. Although for storing the logged in user, its probably not going to work having the user post the key back each time, but rather for the server to set a cookie.

So the key cant be predicted, I use openssl as follows.

public function generate_key($len = 16)
{
    $data = openssl_random_pseudo_bytes($len);

    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0010
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

    return vsprintf('%s%s%s%s%s%s%s%s', str_split(bin2hex($data), 4));
}
over 1 year ago ·
15293
11b2608696511fa5c3398aa9d6af4827

The server doesn't need to store the token. The server only needs to remember the secret key that the token was signed with. That would be most likely stored in some sort of configuration.php and would not change often.

over 1 year ago ·
22276
A13adb6602b46cd5db5f6b5437e0caa4 normal

A little problem...

Using this:
$token = JWT::decode($POST['token'], 'secretserver_key');
echo $token->id;

If you change/forge the token (somebody can do it), jwthelper.php will return a Fatal Error and expose your "secretserver_key" on error message. Have you tried?

over 1 year ago ·
22277
11b2608696511fa5c3398aa9d6af4827

@getuliodtj

  1. Don't actually put your secret key in the code. Store it as an environment variable or define it inside an included file outside of the web root.

  2. Don't have error messages enabled on production. You should be showing users a generic friendly error message with no technical details.

over 1 year ago ·
22278
A13adb6602b46cd5db5f6b5437e0caa4 normal

@rmcdaniel I know that... but donĀ“t you think its better jwt_helper.php return a simple invalid message (when token is invalid) instead of give us a fatal error?

over 1 year ago ·
22279
11b2608696511fa5c3398aa9d6af4827

@getuliodtj No, not really. That's what exceptions are for. You should wrap the code in a try/catch block and handle any exceptions that it throws.

over 1 year ago ·
22280
A13adb6602b46cd5db5f6b5437e0caa4 normal

Now I need to agree with you: a try and catch on UnexpectedValueException solve the problem with elegance =D
Kudos!

over 1 year ago ·
23230
0 m1fisd9hdfx9u7ndwculcjfh2ar9rsngdcmixjwhdcsbyjzdwccltzp3ifo9uyflelmiqert3kaczdz338octjgskkanzdll38ohmpe87lxvt0edsayqjeqlxliqad68eimfoazrfyh

can you say that how to use $_POST['token'] there? because i got "undefined token" error

over 1 year ago ·
25273
None

What's the difference between a JWT and a signed cookie?

over 1 year ago ·
26359
0 oozial zoatj8wjmjadw5qjzmvsj8rw5yadiia9zwoky8immaus5qt3zdoot6jed uslhna4fstpi0tjlmrc6a qcstgi0jvlmrhdrnjwubadd6djhqqskzhg4ese0ek4vnfecp3nxw

Good article, thanks! :)

over 1 year ago ·
28026

@nifrasbcs
Here's the code:
$token = array();
$token['id'] = $id;
$SESSION["encodedScript"] = JWT::encode($token, 'secretserver_key');

Assign the entire encoded script and decode it when you call other APIs.
include('jwthelper.php');
$token = JWT::decode($
SESSION["encodedScript"], 'secretserverkey');
echo $token->id;

9 months ago ·
28331

JSON web token authentication is great and also good for cross domain Here is step by step implementation of JSON web Token http://phpclicks.com/php-token-based-authentication/

5 months ago ·
28793
Arnold

With an authentication scheme like this, you're not able to invalidate a token once it hat granted. If an intruder obtained a password and logged in, he can use the JWT forever. Setting a TTL in combination with asking the existing password to change the password while somewhat solve the most dire cases, but it's still a rather weak in comparison with invalidating sessions server side.

13 days ago ·