8wrxfw
Last Updated: October 07, 2018
·
159.2K
· rmcdaniel

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

19 Responses
Add your response

15276

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

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

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

@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

@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

@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

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

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

over 1 year ago ·
25273

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

over 1 year ago ·
26359

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;

over 1 year 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/

over 1 year ago ·
28793

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.

over 1 year ago ·
29025

@jasny Just change the server key every so often. Of course, you won't have the ability to end user sessions separately but is this even necessary?

over 1 year ago ·
29031

is it ok to save the "secretserverkey" on the php file ?
why is it necessary to have config file for that ?
thanks

over 1 year ago ·
29064

Where does the user stores the json key, sessionStorage?

over 1 year ago ·
29065

can CodeIgniter's encrypt class be used here? if the encrypted string is manipulated and the server tries to decode it It will return null so if the token is null it should kick out the user right?

over 1 year ago ·
29420

Please share code step by step how to save token after subsequent api call,

12 months ago ·
30630

Great idea I'd like to look inside a code to take a deeper look

2 months ago ·