Implementing JSON Web Token (JWT) to secure your app

Percy Pham
Nextzy
Published in
8 min readFeb 27, 2020

An explanation and step by step guide to implement JWT for app’s authentication.

JWT was created to change the way you authenticate your user. In traditional way, you would give your client the session ID, and store session information on server-side, maybe store it in your database or memory cache system (e.g. Redis). But now, with JWT, the information can be stored client-side, it means that you don’t have to store it on the server.

If you don’t know what JWT is, you can watch the video from WebDevSimplified, I found it quite comprehensive:

You can read the official documentation for more info.

For this article, I will use Node and ExpressJS (a server framework) to implement JWT. If you’re not familiar with Node, I’ll try to make it as easy to understand as possible, hope it won’t be a big problem.

A “too simple” way to implement JWT

Simple flow of JWT implementation

Mostly everyone uses this one to start with JWT, basically, all the steps would be:

  1. Client send a login request with username and password to server
  2. Server receive the username and password, authenticate the user
  3. If authentication is successful, then the server creates a JWT token called accessToken that stores user public info and sends it back to the client.
  4. Client receives the accessToken, from now on, client sends any request to server, client just attach the accessToken with request.
  5. Server receives a request, authenticates the JWT token, and continues processing the request, and then returns the result to the client.

Let’s see how does it look like in code.

First, start with the login request from client (browser):

Edit: for storing tokens securely, consider reading articles on that topic, here is an example: LocalStorage vs. Cookies. TLDR: you should read it.

Note: axios is a library to make http request. Read more here.

Basically, what this does is to send a POST request to path /login with username and password, and when server responds with accessToken (JWT token), it stores that accessToken (maybe to localStorage, or any place that you like). If the server responds an error, then alert the user that the login has failed.

When the server receives the request, it needs to authenticate the user and then generates accessToken to send back to client:

The login handler has nothing special here, except the function genAccessToken(user). This is where we use JWT library to generate accessToken :

Note: we didn’t specify the expiration time for the accessToken.

Now, the client has the accessToken now, all it has to do next is just sending request for data that it needs:

Back to the server, when it receives request to routes that require authentication, then it will check the accessToken sent with request, let’s create a middle ware to handle authentication:

Now, we can add it to any route handler that requires authentication:

Note: we have authenticationMiddleware before the request handler

There you go, we have implemented the authentication for our app. Most of the JWT starters use this implementation to authenticate client requests.

But wait, why did I mention it as “too simple” way to implement it? Why is there a word “too”?

Problem that it causes

I overheard a company implementing JWT the way above, their app is a music app, it goes quite smooth at initial time. Until a user complains that his account has been hacked, someone stole his accessToken, and changed his playlist. He changed his password already, but his playlist is still being changed constantly. He contacted the company, but the response is just simply creating a new account.

Why does that happen?

Because the accessToken doesn’t have the expiration time, the one who got the accessToken will have access to the account forever.

To invalidate that accessToken, the company must change the JWT_SECRET_KEY, and that will affect all other users. The company cannot let that happen, so they just have to sacrifice that user’s account.

We don’t want that to happen to our app, right?

A slightly better way to implement JWT

How do we fix the problem above?

The first step is to specify what characteristics that are desirable for our JWT accessToken.

What kind of characteristics that our JWT accessToken should have?

Maybe some of you had a question like: why don’t we just set an expiration time for that accessToken? Make it 14 days or so?
It’s a way to solve the problem, but, there are some apps that have a requirement to make it forever, app users might not want to login again every 14 days, they want it to be available all the time without re-login again. Therefore, I want to keep ‘‘forever accessibility’’ as a characteristic to preserve.

Obviously, to avoid the problem as the music company above, we want to invalidate the old accessToken in some way that does not affect other users.

There is a suggestion that we store the “reference” of that accessToken in memory cache system like Redis, every time the server wants to verify the accessToken, the server needs to check its “reference” on Redis server. And when the server wants to invalidate an accessToken, the server simply goes to Redis server and removes its “reference”. However, this way removes the “client-side storing” characteristic. I don’t like that, I want to keep it on client-side only.

To summarize, there are three characteristics that I want from our JWT accessToken:

  1. Can have no expiration time
  2. Must be invalidated without affecting other users
  3. Must be stored on client-side, nothing is stored on server side

Hmm… , the current implementation already has the first characteristic, so why don’t we just start with the second one: Must be invalidated without affecting other users.

Let’s start with invalidating existing token

Based on the story of that music company, as a user, the thing I want to try first is to change the password. Ok, I want to invalidate all the token when I change the password.

How do I do that?
Well, just simply check the password in every verification step.

Ok, so we have to store the password in token to check it, but won’t it be exposing the password to the client?
Well, we hash it, it won’t be exposed.

Ok, get to the implementation:

Let’s create a hash function first:

We got the hash function now, next thing is to hash the password:

Why I use userId there? Because I want it to be unique for each user. If we just hash the password only, there will be two users that have same password, and theirs hashedPassword would be the same. I don’t want that to happen, so I decided to just combine userId and password together.

And then, we need to rewrite genAccessToken(user) with new implementation:

genKey(id, password) implementation:

Note: we separate genKey(id, password) out to reuse it later.

Ok, that’s the part to create the accessToken. Now, to the part when server checking accessToken sent from client to server:

Phewww! Done, isn’t it?

It seems like all the accessToken characteristics that specified above were achieved and preserved, right?

  1. Can have no expiration time (checked)
  2. Must be invalidated without affecting other users (checked)
  3. Must be stored on client-side, nothing is stored on server side (checked)

But something is not right, now that every request to the server, we must query user from database all the time. This is inefficient, I don’t want it to slow down my server. Therefore, I want to add one more characteristic to those above:

4. Not slowing down server performance too much

A complete better way to implement JWT

Ok, how do I do it?

As I search on the internet and consider for my own app. I came up with an idea like this:

  • We still keep the authentication as the simple way, just verify the accessToken with JWT_SECRET_KEY, we don’t want to check the key every single time.
  • But it still needs to be checked every small amount of time: 5 ~ 15 minutes.

To be more specific:

  • We create an accessToken with the expiration of 15 minutes.
  • We create a refreshToken without the expiration time.
  • If the accessToken is expired, the client can send a new request to get a new accessToken by using the refreshToken.
A complete better way to implement JWT

Let’s implement the new flow.

We start with client sending login request:

Notice that we get an extra refreshToken in response body.

And when the accessToken expired, the client will send a new request with refreshToken to get new accessToken:

Back to the server side, we need to re-implement /login route handler. It has to generate refreshToken and return it back to client along with accessToken:

To the detail of genAccessToken(user) and genRefreshToken(user) :

First, we need to add expiration time for accessToken and remove the key from it:

Note: in genAccessToken(user), the exp in token payload will be added when jwt.sign(payload, secretKey) executes.

Note again: about the type param in tokenPayload, it declares that the token generated is accessToken, this will help to identify which token is being used for authentication. The purpose of type existence will be more obvious when it comes to implementation of authentication middleware.

Second, generate refreshToken and attach key with it:

Note: refreshToken generated doesn’t have expiration time.

Now, because the accessToken doesn’t have the key anymore, the authentication doesn’t have to check for user in database anymore, let’s modify it back to the simple way:

Note: in here, we check to see if the token is access type, since we use the same JWT_SECRET_KEY, so if we don’t have the type to make sure it’s access token, then client can use refreshToken to bypass authentication check.

The last thing to do is to deal with the /refreshToken route handler:

You can see that all the database querying is in here. Authentication doesn’t have to query database for every request anymore.

This way, we have achieved all four desired characteristics for our JWT token:

  1. Can have no expiration time (checked)
  2. Must be invalidated without affecting other users (checked)
  3. Must be stored on client-side, nothing is stored on server side (checked)
  4. Not slowing down server performance too much (checked)

That’s it! We have done a full work flow of initializing and authenticating request with JWT.

The final implementation is in this gist.

Summary

This implementation fits with the current app that I’m working on at the moment. It still has some cons like the invalidation doesn’t affect immediately, but all the pros of it (four characteristics) had me to decide to use it for the current app.

There is no one size fits all solution when it comes to authentication with JWT. If your app has different business requirements, such as having to manage all logged in devices, or just allow only one device login at a time, then you have to come up with your own implementation. I hope that this article will help you to spark some ideas on how to secure your app with JWT.

--

--