Stateless tokens with JWT

Let's talk about use cases. You're tasked with creating a user signup form that sends the user an email for verification. Or maybe you're tasked with adding a forgot my password to login form that sends an email to the user to reset his password. Imagine we had to implement oauth2 and we need to store user session tokens in a database. Let's say we had to create a public API that required each user have their own API keys.

All of these use cases have one thing in common. You have to generate a token and store it in a database. Whether it is a simple password reset token you send to the user or a more complex signup token where you have to store full user information in some database (referenced by the token) or store registrations in our actual users table/storage with a state unverified or something.

This creates lots of dirty data and states in our application. The user signup is especially dirty as we can end up storing multiple user data that will never be resolved. Like unverified emails or signup spam or something. And all of this will fill your database with data we don't want. Sometimes you just have to live with it or you make some kind of cleanup program that cleans out old tokens or registration.

Complexity factor

The most common token storage used is Redis. Redis is a wonderful key-value storage and can be extremely useful to save tokens like the ones we discussed before. It includes features like automatic deletion or expiration for tokens and can store loads of data. This is very useful in all the user cases including user signup where instead of sending the user information in a dirty state to our database, we can use Redis as the immediate holder while it's being verified.

But Redis adds a dependancy. If your company or storage has one available then that's great. But if you don't have one, installing it, managing it, configuring it and maintaining it can add loads of unecessary manhours to a project, just to store simple tokens. Not to mention how Redis stores everything in memory, should someone flood the signup form with spam, could eventually crash and lose everything.

Alternative solutions all have the same problem. You're adding dependancies or services to your setup which you would then need to maintain in the future. The worst solution of all is the one where you add a program of your own kind to cleanup old tokens were you to store tokens in a database that doesn't have expire feature. I don't need to mention how quickly that's gonna hurt you should you need to change the database or the storage or the data as each update would require updating the cleanup program. And lastly, deploying it to server, managing crontab to run it and etc. all add needlessly complication and maintainance nightmare in the future.

How JWT saves your life

JWT or JSON Web Token is an oauth draft proposal for creating and verifying tokens in a form of json. Here are a couple of features for JWT:

  • It allows you to verify token authenticity.
  • It has a json body to contain any variable amount of data you want.
  • It's completely stateless.
  • And more.

Let's break down what JWT is.

Essentially JWT are basic json object payload converted to Base64 with a header and the signature. The header contains information about how the payload is signed, what algorithm is used and what type it is. The signature is a hash of the payload including the header, signed with SHA or any algorithm type specified in the header.

The beauty of JWT lies in the signature. The signature verifies the authenticity of the token based on a secret. And this is where the stateless part comes in: As long as you know what the secret is, you can verify if a token comes from a trusted party (in this instance, your application) or not, without having to require to store the token in some kind of database or lookup table or something.

The payload has all kinds of features. Including things like when it was issued, when it expires, who issued it and so on. In addition, JTW supports encrypting the JSON payload if you don't want anyone to read its contents.

Examples

Let's take the password reset use case we mentioned earler and solve it using JWT without actually storing anything in the database.

A user comes on our site http://example.com/profile/forgot and types in his email address. The server takes that email address and generates a simple JWT with the email in the body as well a specific expiration time. That generates for example the following token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAc29tZXRoaW5nLmNvbSIsInJlc2V0IjoicGFzc3dvcmQiLCJleHAiOjEzMDA4MTkzODB9.K99dko4O1MJ5COthXLzeE6sFZBRO3LW86mkQHs1h-5U  

If you hop on to jwt.io with that token, you can see what the actual contents are.

Token is sent in an email and the user clicks on a link that maybe looks something like this:

http://example.com/profile/reset?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAc29tZXRoaW5nLmNvbSIsInJlc2V0IjoicGFzc3dvcmQiLCJleHAiOjEzMDA4MTkzODB9.K99dko4O1MJ5COthXLzeE6sFZBRO3LW86mkQHs1h-5U  

Et voila. Now all the server has to do is decode the token, make sure the signature is valid with the correct secret and allow the user to type in a new password.

That's it: No fuzz, no database storing tokens, no nothing. And the good part is, the token automatically expires due to the exp we set in the payload. There is no way someone could forge a new token however because it would require the malicious user to know our secret.

If you're extra paranoid, you could make the secret for the password reset include his user id as well as maybe a SHA2 of his current hashed password or something. That way, the signature will fail automatically after he changes password.

The use cases don't end there though.

We could go one step further with JWT and make a user signup that creates a token with his registration inside the paylod. That way we don't have to temporarely store a user signup in a database. As an example, check out this token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoic29tZXVzZXIiLCJlbWFpbCI6InRlc3RAc29tZXRoaW5nLmNvbSIsIm5hbWUiOiJUZXN0IFVzZXIiLCJwYXNzd29yZCI6InVzZXJwYXNzd29yZCIsImNvdW50cnkiOiJJY2VsYW5kIiwiZXhwIjoxMzAwODE5MzgwfQ.QTDcx2GpbzVzfUpQ-QuFhuEgQEfAOa1V2meiAEUqnko  

There is however one problem with the above token. It has the password the user typed in inside the token that can be easily decrypted, should someone get his hands on the token. However there are two solutions to this:

  1. We can encrypt the payload and therefore nobody can read the data
  2. We can have the password be typed in after verifying the email.

The second makes the signup process a little more complicated as the user would have to type in some forms twice. The former however would simply work.

Simplified oauth/API keys process

By nature, should we have an oauth2 authentication on our site or API, whenever a user would authenticate, the old method required you to generate a token and store it into the database along with the permissions that token is allowed to do. Or in the API keys use-case, we would look up the api keys to see if they match.

This adds tremendous complexity to our application, requiring us to always do a lookup on every request for the token/keys as well as to verify the permission tied to that token/keys.

This is where JWT comes really strong in. Using it as a bearer for authorization, you can statelessly verify if the user is authenticated by simply checking if the expiration in the payload hasn't expired and if the signature is valid. The biggest feature for this is we can additionally include all the permission sets inside the payload to make permission checking seamless.

More information

This is just scratching the surface of what JWT can do for us. The possibilities for JWT are endless and can include wide variety of solutions to any problem. I'm gonna list a couple of resources here that helped me along the way.

Auth0 blog has many blog posts about JWT and I recommend reading them. Most of them are directly tied to the service they provide but it gives an example of the powers JWT can give you.