Implementing JSON Web Token (JWT) to secure your app
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
Mostly everyone uses this one to start with JWT, basically, all the steps would be:
- Client send a login request with username and password to server
- Server receive the username and password, authenticate the user
- 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. - Client receives the
accessToken
, from now on, client sends any request to server, client just attach theaccessToken
with request. - 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
:
- Can have no expiration time
- Must be invalidated without affecting other users
- 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?
- Can have no expiration time (checked)
- Must be invalidated without affecting other users (checked)
- 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
withJWT_SECRET_KEY
, we don’t want to check thekey
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 newaccessToken
by using therefreshToken
.
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:
- Can have no expiration time (checked)
- Must be invalidated without affecting other users (checked)
- Must be stored on client-side, nothing is stored on server side (checked)
- 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.