Migrating away from JSON Web Tokens for sessions

We’ve all read a lot on the web about the fact that JWTs are not meant to be used for sessions for multiple reasons, but I’ve yet to see posts about how to actually migrate away from them in our applications.
So here I am, writing an article explaining how I did it on an Express.js API using express-session
, and how you can (and should) too.
Note: I am not going to cover the “JWT vs session cookies” topic in this post, as other writers have already done it in way better manners than I could ever think of. I’ll point you to joepie91’s article from 2016 if you want more information on the reasoning behind this: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
Describing my current authentication flow
As we all implement authentication and sessions in a different fashion, I will try to keep my samples simple (what a pun), so that you don’t need to navigate through my coding style to port it.
For this article, I will assume that there are only two places where you use JWTs.
- The first one is during authentication, where you’d create the JWT and return it either in a cookie or a payload.
- The second one occurs when authorizing your users to access your routes in a middleware, where you’d check if there’s a JWT in the request, and if it’s valid.
Authentication on the API
My authentication flow is simple, one route to create the session (login), and one to delete it (logout):
Authorizing the user to access your routes
As for authorizing the user to access private routes, I check his token and his account in the database:
Code snippets breakdown
Looking at my snippets, you can see that I use my JWT to store the user id, and not user data. The reason behind this is that JWTs are base64-encoded payloads which tend to grow fast, so I wanted to keep this one as small as possible to avoid slowing down requests.
The consequence of this lies in the middleware, where I need to query my database to get more information about the user, and store it in req.currentUser
. It’s just to be able to use it later in my controllers.
Thinking about it now, I don’t feel like it’s the best idea I’ve had in my tech life, but I never benchmarked it. Premature optimization? ¯\_(ツ)_/¯
Migrating to session cookies using express-session
To replace my JWT cookie, I decided to use express-session
, which used to be the official session middleware back on express v3.
You can use cookie-session
, which is a smaller package that’s also recommended by the express team (see https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely), but it’s less popular and I prefer to have my session data stored server-side.
Setting up the middleware
As stated in express-session’s documentation, you need a dedicated store for session data in a production environment. Redis was my choice as it’s reliable, fast, and well-supported.
Migrating the authentication flow
The migration of my authentication flow was flawless, as you can see in the diff below.
There’s nothing more I needed to do because express-session handles everything internally when you touch the req.session
variable! If it’s the first time you touch it, then it will automatically create the cookie and initiate the session in your store (Redis in my case).
Migrating the isAuthenticated middleware
Migrating my isAuthenticated middleware was as easy as it was for the authentication flow. I just removed everything related to the JWT!
As you can see in the diff, I didn’t move my req.currentUser
data to the session data, because here in this context I need fresh information to ensure that the user is still allowed to proceed.
What I did though, was moving some of his “unlikely to change” information to the session, so that my database query stays small. It doesn’t show here because it’s specific to the project I’m working on, and isn’t related to the migration itself! I may move it to Redis in the future, only time will tell.
That’s pretty much it. We’re done. Wasn’t hard!
Post migration thoughts
There’s only one thing that came to my mind after migrating, and it’s about security. Having moved to session cookies, with sessions stored in Redis, we will now be able to revoke sessions whenever we want.
Had I kept using JWTs, I wouldn’t have been able to do this without an ugly table containing active sessions. In case you wonder why (if you do wonder, then you really need to migrate AWAY from them), it’s because there’s no revocation mechanism in JWTs, those just haven’t been designed for this usecase.
Nota Bene: Yes, I don’t use semicolons. See https://standardjs.com/ for more information.