Update on 17 April, 2021: Don't miss checking out all those comments posted to this article. They are all insightful, more than this article itself. I thank those of you who took time to write their insights. I'm now learning more about web security, to be able to digest these comments.
There's a countless number of articles across the web that compare session tokens and JSON web tokens (JWTs) as a method of user authentication. It's really confusing for someone who is trying to create his/her first full-stack web app (i.e. me myself). No one clearly explains pros and cons of each. (Maybe no one knows for sure).
Spending several days doing research on the topic, I've come to a conclusion that I will go for session tokens, because JWTs are not meant to be used for repeatedly authenticating the same user.
In a week's time, however, I'm sure I will forget why I've come to this conclusion. This topic is really complicated. This article is a memorandum for my future self and hopefully for you as well.
Disclaimer: I'm not a web security expert. What's written below may not be fully correct, although I do my best to get everything right. Post a comment if you think I'm wrong. I just want to know the truth.
I'll skip describing what session tokens and JWTs are. There are great explanations already out there.
For session tokens, I recommend reading Kamani (2017), who provides the most concise introduction to session tokens I've seen (and why cookies are necessary for them).
For JWTs, have a look at Calandra (2019), who very well explains the context in which JWTs were born.
To compare session tokens and JWTs on a level playing field, we first need to be clear about one thing about JWTs:
Many tutorials on JWTs that I've seen demonstrate how a front end app can store JWTs in localStorage, and these tutorials always attract comments saying, "Don't store JWTs in localStorage, which is susceptible to cross-site scripting (XSS) attacks". I find Chenkie (2020a) extremely clear about what these comments really mean, with a rare demonstration of how XSS attacks can be done. (It requires a sign up (free of charge), but this 2.5-minute video tutorial is really worth watching.)
So JWTs should be stored in a cookie, just like session tokens need to be stored in a cookie. Cookies are susceptible to another kind of attacks known as cross-site request forgery (CSRF), but we can deal with it (at least partially, if I understand correctly) by using a library like
csurf (see Chenkie 2020b for detail).
NOTE: Vladimir and Gopal (2019) advocate storing JWTs in memory, which is neither susceptible to XSS nor CSRF. Maybe this approach is worth checking out.
So the relevant comparison for us is "the session tokens stored in a cookie" versus "the JWTs stored in a cookie".
The major drawback of session tokens is that the logged-in users will get kicked out when either a different server deals with their request (when an app is served by several servers) or the same sever gets restarted (for fixing bugs etc.), because whether or not the user is authenticated is kept in a particular server. For detail, see Calandra (2019) for example.
This problem can be solved by using a "session store" library such as
express-session's documentation for the list of session store libraries for Express.js). The idea is to keep session tokens in a dedicated database, and each time the user makes an HTTP request with their session token, the server receiving the request will consult this database to learn whether the request comes from the authenticated user or not. Performance suffers consequently, as retrieving data from a database always takes some time.
Using session tokens requires a Redis or a database. Image source: Awad (2019)
JWTs solve this issue by making a server "stateless": the user’s authentication status is embedded in the token so the server doesn’t need to remember about it. Another server or a restarted server can tell whether the user is authenticated or not, by decoding the JWT sent along with each HTTP request. So the logged-in user won’t get kicked out. We don't need any database to store user authentication status. Performance won't suffer consequently.
The downside of JWTs, however, is that the server, once issuing a JWT, cannot control when the token expires. Even after the user has logged out, an app cannot kill it with the server code or with the front-end code (as JWTs should be stored in a HTTP-only cookie for security).
The longer the token lives, the higher the risk of getting it stolen. So we want to set a JWT to expire in a short period of time, say, 15 minutes. Then the user experience suffers: each user will be forced to log out while using the app every 15 minute. That's terrible.
Extending its expiration date (i.e. refreshing the token) requires a "refresh token" to be issued to the user as well as being stored in a database. When the token expires, the server will check the refresh token sent with a HTTP request and verify it against the one stored in the database.
JSON web tokens require a Redis or a database for storing refresh tokens. Image: adapted from Awad (2019)
This sounds familiar... Yes, it's the same as session tokens, which need to be stored in a database for different servers to verity the user authentication status. JWTs should have been a solution to this problem, but now we're back to the same situation: accessing database to verify the user authentication status. And it's more complicated: we're now not checking the token itself, but checking the token issued to refresh the original token... This is why Awad (2019) says he uses session tokens, not JWTs, for a simple web app.
On the other hand, session tokens can be destroyed in the server code, say, when the user logs out. Extending the validity of sessions is straightforward, just by setting the
rolling option to be
So the major benefit of using JWTs, improving performance by ditching the database of session tokens, is completely overturned by the need of a refresh token database to avoid forcing the user to log out every now and then. Well, not completely, to be exact. Awad (2019) points out that the frequency of database access is minimized with JWTs: the server needs to consult the refresh token database only when a JWT expires while it needs to access to the session token database each time the user sends a HTTP request. But this difference becomes smaller and smaller with a shorter lifetime of JWTs for strengthening security.
Once I understand all this, then I finally figure out what Slootweg (2016) means by saying JWTs are not meant to be used to manage user sessions. By design, JWTs are not suitable for repeatedly authenticating users. The benefits of JWTs will be compromised once we start dealing with user sessions.
That's how I have interpreted the difference between session tokens and JWTs. If I miss something, please let me know. I'm eager to learn more.
This article is published in a publication called Web Dev Survey from Kyoto. The idea is to hold a virtual conference on web development in Kyoto City, the ancient capital of Japan, where I’m living now. After each article (which acts as a seminar talk), we take you to the tour around the city.
Today the famous cherry blossoms are mostly fallen, unfortunately. However, kerria is now blooming in brilliant yellow across many places in Kyoto City:
Daikaku-ji Temple (photographed by the author on 5 April 2021)
Matuno'o Shrine (photographed by the author on 10 April 2021)
Ben Awad (2019) "Why I haven't been using JWT tokens for Authentication", YouTube, Apr. 21, 2019.
Mariano Calandra (2019) "Why do we need the JSON Web Token (JWT) in the modern web?", Start It Up, Sep. 6, 2019.
Ryan Chenkie (2020a) “Steal a JSON Web Token”, React Security Fundamentals, May 2020.
Ryan Chenkie (2020b) "Add a Cross-site Request Forgery Token", React Security Fundamentals, May 2020.
Soham Kamani (2017) “Web security essentials — Sessions and cookies”, sohamkamani.com, Jan. 8, 2017.
Sven Slootweg (2016) “Stop using JWT for sessions”, joepie91’s Ramblings, Jun. 13, 2016.
Vladimir and Tanmai Gopal (2019) "The Ultimate Guide to handling JWTs on frontend clients (GraphQL)", Hasura Blog, Sep. 9, 2019.