Last Updated: June 26, 2023
·
230K
· 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

21 Responses
Add your response

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 ·

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 ·

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 ·

@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 ·

@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 ·

@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 ·

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

over 1 year ago ·

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

over 1 year ago ·

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

over 1 year ago ·

Good article, thanks! :)

over 1 year ago ·

@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 ·

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 ·

@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 ·

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 ·

Where does the user stores the json key, sessionStorage?

over 1 year ago ·

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 ·

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

over 1 year ago ·

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

over 1 year ago ·

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).

over 1 year ago ·

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

over 1 year ago ·

JSON is much better than Session cookies, We've been using it since we saw this post, however I've not that familiar with the supporting components but still it works fine for us.

over 1 year ago ·