8wrxfw
Last Updated: July 25, 2019
·
196.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 ·
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,

over 1 year ago ·
30630

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

over 1 year ago ·
31562

The https://github.com/Spomky-Labs/jose library is substantially faster than the one you posted. And yes, APIs are supposed to be stateless. Sessions are fine when you're working with a web browser. Even more secure, however, is a variant of the JOSE standard referred to as PASETO, which closes some security loopholes in the original spec. https://paseto.io

Lastly, please don't use this helper class. It's written for PHP 5 which is entirely EOL at this point. Reviewing the code, it looks like it was originally written for PHP 5.2, which came out around 10+ years ago. Not to be mean to the author; we've all written code for PHP 5 before, but its time has come and gone. The code referenced in this post needs to be modernized before it's useful for versions of PHP that are still supported (7.1+ at the time of this writing).

9 months ago ·
31935

SON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. JWT.IO allows you to decode, verify and generate JWT.
{
"alg": "HS256",
"typ": "JWT"
}

---payload ---
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
get video tutorial on vidmate.tube

7 months ago ·