DEV Community

Masa Kudamatsu
Masa Kudamatsu

Posted on • Updated on • Originally published at Medium

JSON web tokens are NOT meant for authenticating the same user repeatedly: Use session tokens instead

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.

For the uninitiated

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:

Don't store JWTs in localStorage

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".

Cons of session tokens

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 connect-redis (see 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.

Alt TextUsing 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.

Cons of JWTs

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.
Alt TextJSON 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 true with express-session.

Summing up

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.

Before we go…

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)
Alt Text

Matuno'o Shrine (photographed by the author on 10 April 2021)
Alt Text

References

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.

Top comments (53)

Collapse
 
adrach profile image
Andrew Drach

I am sorry for sounding harsh, but everything written here is highly misleading and extremely dangerous. I would plead with the author to do more research and update the contents of the article. No, session tokens should not be used today under any circumstances unless the developer is lazy or have not caught up on basics of web security in the past decade. And no, it is completely false that JWTs do not have expiration. It is actually their main feature that made the popular. On top of it the same token allows to take care of both authorization and authentication which is a very nice add-on. Furthermore, there are established procedures for token revocation, and JWT allows for seamless horizontal scaling. Short-lived token auth with automatic token update on every API call is the only secure way to battle intercept or leaking of auth info because it invalidates the token on the next response. Refresh tokens are what is used to allow long-term access to the resources and storing those requires extra care. All of it is well established and formalized in OAUTH 2.0 standard
en.m.wikipedia.org/wiki/OAuth

Collapse
 
adrach profile image
Andrew Drach

PS JWT do not need to be ever stored in any DB as they provide stateless access and we just beed to verify signature, so no, nothing like session tokens

Collapse
 
jessekphillips profile image
Jesse Phillips

Any chance you could produce a counter post.

The author didn't say JWT tokens were to be stored in a database, that was in context of a refresh token.

Maybe putting an article together to tackle the actual content of this one will help everyone be clearer on what is the correct process and why.

Collapse
 
branislavlazic profile image
Branislav Lazic • Edited

Then start elaborating yourself. Why session tokens shouldn’t be used? The point I see that OP made is actually fantastic. There is no advantage in using JWT over session based auth for simpler architectures. The whole implementation of stateless token authentication seems miles more complex in comparison with session based auth. The whole internet cannot agree whether access tokens should be stored in cookies or local storage, then it cannot agree whether the token should be stored/blacklisted server side or not, then how refresh tokens should be stored. Not storing JWT access token server side makes it hard for immediate invalidation which then, makes JWT way less safer than session.

Collapse
 
andreidascalu profile image
Andrei Dascalu • Edited

Errr, jwt shouldn't be stored neither in cookie or localstorage. They should be stored in memory (there's no perfect solution though, even cookies are vulnerable to csrf)

You should never need to invalidate jwts immediately, they should expire fast (a few minutes). To invalidate logins instantly there are a number of ways to do it. My favorite is an in validation marker. Any token with expiration date after an in validation marker will be rejected alongside any attached refresh token thus forcing the user to authenticate again.
You should never need to carry state in tokens. Tokens should help identify state for the backend.

Thread Thread
 
branislavlazic profile image
Branislav Lazic

Storing JWT access token in memory will make it vulnerable to XSS attacks. Fundamental of web security implementation is to presume that potential attacker knows how your implementation works. Storing access token in a cookie with httpOnly flag is a way to go. JS is unable to access httpOnly cookie. JWT indeed cannot be invalidated immediately without persisting some state which indicates that the token is invalidated or by rotating secret key used for its signing.

Thread Thread
 
andreidascalu profile image
Andrei Dascalu

Actually it's about the same. Nobody needs to "get" your token. Cookies are being sent on client side requests. Xss doesn't need to read your token, it needs to make requests on your behalf and can do so if your token is stored in cookie.

Collapse
 
samjakob profile image
Sam (NBTX) • Edited

No, session tokens should not be used today under any circumstances unless the developer is lazy or have not caught up on basics of web security in the past decade.

According to whom? Session tokens aren’t inherently insecure even if some implementations are.

And no, it is completely false that JWTs do not have expiration. It is actually their main feature that made the popular.

Nobody said they didn’t? He stated in the article that they have an expiry. The issue he’s illustrating is that you either have to choose between longer expiry durations (which is less secure and means you cannot manually expire the tokens) OR setting short expiry durations and essentially having little to no benefit over a session token because you have to access the refresh token and then generate a new JWT.

Short-lived token auth with automatic token update on every API call is the only secure way to battle intercept or leaking of auth info because it invalidates the token on the next response.

Well this is patently false. It’s ‘secure enough’ but so are many session token implementations - and they’ve been industry tested and used without fault. Short-lived tokens are still prone to their own security flaws such as the refresh token being leaked and at some point you’re going to have to exchange your shared secret or refresh token.

Collapse
 
frankszendzielarz profile image
Frank Szendzielarz

I agree with you. I think it would also be good to elaborate on Refresh Token purpose in your response. As you know, the Refresh Token is used to separate the responsibility of access revocation from the 'resource provider' and put it back on to the authorisation server / identity provider, where it belongs. If somebody loses their mobile device and wants to revoke access, this can be done by notifying the identity/auth provider, who revokes the refresh token. The backend API providing the resources cannot easily do that without for example either maintaining its own access logic (overlap of responsibility with auth provider) or revoking signing keys (invalidates all other access tokens too). I think this knowledge is implicit in what you write but would be best made explicit.

Collapse
 
siddhantk232 profile image
Siddhant Kumar

To revoke a JWT, you can change the secret you used to sign those tokens, this invalidates all the tokens you issued. This works like a master reset for you service in case your tokens got compromised.

Also, when signing a token, using a version identifier in token's data can be useful whenever you want to revoke this token for a specific user. So if a user "forgets his password" or logs out, you can change the version identifier in the DB. Now, if the user tries to log in with his old JWT, you will know as the version of his JWT and version stored in DB are not the same.

This is my understanding of using JWTs for web authentication but I totally see the point of using sessions. In most cases I also prefer to use sessions but knowing how to solve the problem of invalidating JWTs is still helpful.

Collapse
 
samjakob profile image
Sam (NBTX)

Doesn’t setting a version on the JWT invalidate the point of JWTs because at that point you still have to do the lookup every time the JWT is used to make sure it’s valid?

Collapse
 
siddhantk232 profile image
Siddhant Kumar

To be honest, I do not have a proper answer to this. You are right, I do have to talk to a DB every time to verify a JWT. This is also the case in session-based authentication where you check the session to see if the user is authenticated.

Now the possible solution I can think of, based on my recent readings, is:

You implement a refresh_token, save it in the client as a strict samesite, httponly cookie with a specific path on your server so that this cookie is not sent with every request. Then on your server, you can version this refresh_token instead of the acces_token and can invalidate refresh tokens by changing the versions. This approach should have the access_token to be short-lived. So even if your access_token is compromised (highly unlikely) it will be only valid for a short period of time. Again, this is similar to session-based auth but with this, you are completely stateless with your aceess_token, that is this access_token can be verified without any DB calls until it is expired. When the access_token expires, you request a new one using the refresh_token.

If you are using asymmetric JWTs, then you can also share this access_token with other services without sharing the signing secret and they can verify if the token is published by a trusted source. One way to do this is by using JWK(S).

This is how I think OAuth also works. The issuer (aka authentication server) first authenticates you and manages your session on their servers. Then, you can request an access_token and refresh_token (depends on the grant type). When you have the access_token, you can share this token with relevant services that know how to verify this token.

I do not know if this is the best answer and I am not sure how I feel about giving access for a short period of time even if I have revoked the refresh_token but I have read about people using this approach. So, in the end, you are making DB calls but they are not on every request, you make DB calls only when the acess_token expires.

I hope this makes sense.

Thread Thread
 
samjakob profile image
Sam (NBTX)

Gotcha! That all makes sense and I understand the approach but really this is my sorta quandary when it comes to JWTs, like you said:

I do not know if this is the best answer and I am not sure how I feel about giving access for a short period of time even if I have revoked the refresh_token but I have read about people using this approach. So, in the end, you are making DB calls but they are not on every request, you make DB calls only when the acess_token expires.

I feel like it ends up being essentially a glorified session token simply because the access period is so low you might as well just check it on every request. (The idea of having it available for a short period after revocation - no matter how long - really doesn’t sit well.)

I feel like there’s a use case for JWT but it certainly isn’t a be-all-end-all to simply replace the session token in all cases.

Collapse
 
rihan profile image
Rihan

Including a version is something I've never thought of and will change my life, thanks!

Collapse
 
blackr1234 profile image
blackr1234

You don't have to add a version number as one of the JWT claims. You can simply check if the issued-at value is before the updated-at value of your user record. If it is, that means the user record has been updated after the token is issued, so the user should login again to get a new token.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

I just had an "Aha" moment. Thanks!

Collapse
 
wsedlacek profile image
William Sedlacek

I think that both solutions have merits but are designed to work best for different scenarios.

JWTs really shine when you have a dedicated or 3rd party issuer like Google or GitHub or even your own solution that can issue a JWT that can be used with another application. The refreshing of the JWT can be managed by the issuer which would be directly talking to the frontend (think serverless).

Sessions on the other hand are really for when you are communicating with a single server (or a cluster using the same store) once you introduce external services or even micro services things start to get more complex or even impossible.

It's about using the right tool for the job and understand how those tools work.

Collapse
 
titi profile image
Thibault ROHMER • Edited

Take a look at this article:
dev.to/gkoniaris/how-to-securely-s... and its comments (i made one)


Session tokens and JWT are different beasts with pros and cons. You identified some of them.
I'm assuming you're trying to authenticate a user on a web app.

For ages, session token were the way to identify a user on a website. It's still the case for most sites.
Like the "ASP.NET_SessionId" cookie in a .Net Framework website for example. The cookie simply contains an id, thus the network overhead is very small.
The id is stored in-memory from the server that generated it, and an in-memory dictionnary is provided to store whatever you want for that session.
It thus makes things easy to store things while a user is browsing your website, like a shopping cart or the last form choices...

It's easy, but still highly unsufficient for a decent secure application because:

  • If you copy this id and inject it into requests from another computer, you are automatically logged in
    Counter measures: httpOnly attribute, csrf token, store info like ip adress or user agent at session creation, and check it doesn't change on each request, ...
    See more : cheatsheetseries.owasp.org/cheatsh...
    [if you're using ASP.Net forms authentication ticket, check that the the sessionId is associated to the same forms authentication ticket]

  • It doesn't provide a "remember me" option in the login form:
    When you close your browser, the cookie is destroy thus you loose authentication
    A "remember me" option requires to store a cookie with an explicit date that your browser keep.

  • Scalability is an issue. As soon as you have 2 servers (which you should for an high availability website), you need sticky sessions to load balance requests to the corresponding servers. Which make it challenging to load balance users across servers.
    That's the main issue with session token: it's not stateless
    Counter measures: session store like you said. Which is easier nowadays with fast system like redis.
    But it's still challenging in some web app because you also store some data locally on the server between requests and it breaks without sticky session


JWT is more recent and allows you to exchange claims between 2 parties and verify the integrity of claims.
It means:

  • this time, we can store key:value pairs inside
  • each token can be validated on server side with the signature (this prevent a fake forms authentication ticket)

This allows to make stateless requests : any server can validate the request (and use info for the jwt payload).

As you correctly identified, there are 2 main downsides:

  • refresh tokens allows you to genereated as many access token as you wish. thus even though they are not used often, their storage is critical.
    it's hard to prevent them to be stolen or brutforced

  • There is no expiration mecanism by default
    For this, you'll have to rely on your library or code it yourself.
    Take a look at this python library it's cool: pypi.org/project/Flask-JWT-Extended/
    I invite you to take a look at this page to get the refresh concepts: flask-jwt-extended.readthedocs.io/...
    Basically this idea is to create an access token a few minutes before expiration.

But also:

  • A JWT token with a big payload (lots of key:value) will add significant network overhead for EACH REQUEST

  • A copied JWT token is valid. Even if the user hit logout in his browser: the token can still be reused.
    (again the issue might occur with xss: an injected script might copy the token to a bad actor website)
    Similar to the session id we have to use counter measures here. Csrf token is a must.

One other idea is the blacklist: as soon as a user logout, you add this jwt token to a blacklist.
Which requires a redis-like server also...


So, were to go from here?
It all comes down to what you are trying to achieve...
Is this a web app?
How often and long the user are connecting?
Do you want a remember me feature?
Can a user be connected from 2 locations at a time?
Do I want scalability with stateless applications that i can kill/restart without impacting users?
Is there also a REST api with authentication? Is the REST api called only from your website or also from the public?

For a simple calendar application where members can authenticate and make comments, I used a flask python app with Flask-JWT-Extended.
This idea was to play with JWT rather than session based token.
There is also a REST api used by the web app itself.

With session based token, there was a notion of "sliding expiration" which doesn't exist with jwt:
If the user keep making requests, the server in-memory session expiration is pushed back in time. This is not easy with JWT (needs refresh token).

I decided not to use refresh token because it's not practical in my case.
When the user login, a 1H token is created. If the user keep browsing and make a request 1H and 1s later, he's immediately disconnected.
That sucks but 1H is ok for my app usage. And i don't have to manage refresh tokens.

On the login form, there is a remember me checkbox. I decided to create a 30days access token in that case.
It's not the best but hey good luck bruteforcing that jwt token^^
You can also add counter measures to bruteforce attemps (slow them by responding slower and slower).


My REST api does can use both cookie tokens or http header token.
flask-jwt-extended.readthedocs.io/...

If you just want to play with the REST api from an external client: no need for cookies

  • i got an enpoint /api/auth/login to generate an access token
  • it is returned as json response, NOT cookie
  • access token valid for 15min only, without payload.
  • each subsequent requests need to specify the token with "Authorization: Bearer " header

The web app can use the rest api by forwading automatically the cookie token (same domain).
But xhr request should also transport the csrf token like so:

var csrf_cookies = document.cookie.split('; ')
  .map(x => x.split('='))
  .filter(x => x[0] === 'csrf_access_token');
if (csrf_cookies.length)
{
  csrf = csrf_cookies[0][1];
}

var xhr; // make ajax request
//...
xhr.setRequestHeader('X-CSRF-TOKEN', csrf);
Enter fullscreen mode Exit fullscreen mode

see there doc. : flask-jwt-extended.readthedocs.io/...

Hope that provides some answers!

Collapse
 
stevepryde profile image
Steve Pryde

I don't think you actually explained why jwts shouldn't be used.

It's extremely common for them to be stored in local storage. Can you give an example of a xss attack?

It's also very common to store them in a db on the server, using jwt for both auth and refresh tokens. The db is accessed on every request but you said session tokens require this anyway. You can store tokens in redis for improved performance.

If the user logs out, you can delete the tokens from the db+redis. So jwt lifetime won't matter.

Collapse
 
lafayetteduarte profile image
Lafayette Duarte

This article here is quite interesting on the subject
blog.duendesoftware.com/posts/2021...
"It’s not only your own code that must be XSS-proof. It’s also all the frameworks, libraries and NPM packages you are pulling in (as well as their dependencies). And even worse, you have to worry about other people’s code running on your host. The recent work around Spectre attacks against browsers illustrates nicely that there is more to come."

Collapse
 
stevepryde profile image
Steve Pryde

Fair enough. Thanks for that.

I just think that cookies are a horrible solution and will break for anyone who disables cookies on your site. There's also those awful cookie accept banners that plague the web these days. I'd very much like to get away from these.

The attack vector from npm packages isn't really any different in the browser than on the server. Your server language might not be JS but if you still rely on 3rd party code then the same issue applies to every piece of software. I think webassemby may also mitigate this to a degree in the browser as well.

Thanks for the link though. Very useful.

Thread Thread
 
stevepryde profile image
Steve Pryde • Edited

Here's another take, and talks about an aspect that I also think is more important - avoiding XSS attacks in the first place: pragmaticwebsecurity.com/articles/...

If you're running untrusted or compromised code in your app, it's basically already game over. All of these approaches such as cookies and web workers seem to be avoiding the real problem and it seems more like security via obscurity (which is no security at all) - hoping the XSS can't find where we put the tokens or our mechanism for retrieving them. Bottom line is that if your application code can do something, then (in all cases I've seen so far) so can the XSS code since it's running in the same space.

I'll continue reading on this because the topic interests me. I'd prefer to see better solutions that focus on eliminating XSS rather than assuming that all browser code is compromised.

Thread Thread
 
lafayetteduarte profile image
Lafayette Duarte

Yeah . Completely agree that the cookie approach is quite weak one. I would usually go for the openid / oauth approach . It's hard to find good implementation of the specifications but it's worth the effort.
I do use an implementation of Identity Server at work and it's really good.
Other point made on the article that you cannot invalidate the current Auth session could be relatively easy to fix by using reference tokens, which sounds very similar to the proposed approach here but using the oauth specifications
.identityserver4.readthedocs.io/en/...
In extreme scenarios a proof-of-posession such as mutual TLS could be an interesting approach.

identityserver4.readthedocs.io/en/...
I'm yet to try this one

Thread Thread
 
lafayetteduarte profile image
Lafayette Duarte

Yeah . Completely agree that the cookie approach is quite weak one. I would usually go for the openid / oauth approach . It's hard to find good implementation of the specifications but it's worth the effort.
I do use an implementation of Identity Server at work and it's really good.
Other point made on the article that you cannot invalidate the current Auth session could be relatively easy to fix by using reference tokens, which sounds very similar to the proposed approach here but using the oauth specifications
.identityserver4.readthedocs.io/en/...
In extreme scenarios a proof-of-posession such as mutual TLS could be an interesting approach.

identityserver4.readthedocs.io/en/...
I'm yet to try this one

Collapse
 
branislavlazic profile image
Branislav Lazic

Storing them in local storage makes them indeed vulnerable to XSS attacks. If you think about old fashion way of XSS when an attacker submits a malicious JS code through some field... naha... we’re past those days. Every modern JS project is using a ton of 3rd party dependencies. And there’s your source of XSS attacks. Yes, you can store both access (I really don’t know why people constantly call them JWT, since JWT is just a format) tokens and refresh tokens in database, but what’s the advantage over session based auth?

Collapse
 
justintime4tea profile image
Justin Gross • Edited

The problem with most articles that most people are reading is that they aren't articles about security, what it means, or what it should look like. Part of the problem is that people go looking for these exact articles (ie: how do I do security). Not only that, most resources for the why, how it works, examples of in the form of real production "grade" solutions, and the like are dismissed because "I'm just making a POC" or "I don't expect any users", or "I know enough, it's just auth! How hard can it be? It's like user input and hashing right?", or "I'm just learning", "I'm just looking for something simple or easy", etc.. etc... etc... That's all great. If you want to learn how databases work you can totally write your own but you should probably, for the sake of your users, simply use an existing database for a "real" product. Unless of course your product in fact is a database. This is very similar to the countless individuals who cowboy code their way to success rebuilding wheels and reinventing concepts (generally very poorly) because the resources they were searching for were how to do something (how do I use JWTs, how do I access redis in node, etc...) instead of why you would want to do something, what it means, how it works, what things have evolved over time by a collective of incredibly smart people that I can learn from by their example, etc... Emphasis should be on learning and understanding not just on doing. Too many people skip straight to "how do I do?" Those "how I do" articles rarely if ever go over how you should do it essentially teaching you the bare minimum, at a very low bar of quality, absolute minimum to do XYZ and rarely talk about what it means and why you shouldnt do this tutorials and that stack overflows copypasta's in a real product.

All of that being said there are numerous resources for understanding what security is, can be, why it's important to standardized it, and there are several organizations which have been thinking about, studying, and evolving security concepts and standards for years and years. These are the resources one should look to when trying to understand what security means, which standards and expectations to follow, and how they may fit for a given product or company. If you're interested in understanding what security is, what it means, and how it may fit into your product, in the context of identity and authorization, I implore you to check out the following couple resources and start looking for resources which aren't just tutorials and "how I do" articles.

identityblog.com/wp-content/images...

openid.net/2021/04/10/the-7-laws-o...

idsalliance.org/

Collapse
 
lordofcodes profile image
Sujeet Agrahari • Edited

Ben Awad seriously? It would have been worth reading if you would have referenced owasp guidelines on JWT auth .

cheatsheetseries.owasp.org/cheatsh...

One more thing, the whole point of JWT is to make auth stateless and should not be stored on server.
It fulfils REST constraints that requires requests to be stateless.

Collapse
 
sean_snd profile image
Sean Nicholas

Imho storing the jwt in a httpOnly cookie does only increase security a little bit. It only prevents stealing the jwt.

But an attacker using a xss vulnerability in your app has still full access and could easily do every attack from within your app with your credentials assigned.

Changing the attack from stealing a jwt to running the malicious request directly in your browser is pretty easy. So if you think you are save with httpOnly you're unfortunately wrong.

That said: security is a layered approach. The more layers the better. But I think this specific layer does not add a big barrier for an attacker and does make other things harder (for example reading the content of the jwt).

I would still save my jwt in local storage. If you have a case vulnerability you are screwed anyways 😅

Collapse
 
jonathanihm profile image
Jonathan

I disagree, localStorage is a few factors more insecure than httpOnly cookies. Obviously neither way are immune to XSS 100%, but I feel the statement "a little more secure" is inaccurate. Yes, it depends on implementation but naturally the httpOnly cookie would see XSRF attacks rather than an XSS.

stackoverflow.com/questions/348176....

Cheers!

Collapse
 
sean_snd profile image
Sean Nicholas

Hey Jonathan,

the answer in the stackoverflow article you linked does explain exactly what I mean: "So in reality you are still susceptible to XSS, it's just that attacker can't steal you JWT token for later use, but he can still make requests on your users behalf using XSS."

If you have XSS on your site it does not matter where you put your JWT. Because the attacker can send requests on behalf of the current user. Only thing is he can't copy your JWT and use it on his computer. But no need for that, he can just use the XSS vuln to send a request to /admin/drop-all-the-tables or get data from /admin/users and send it to his server.

It's just a bit more secure as it is not as easy for the attacker as querying the localstorage and sending it to his server. But the overhead to write a XSS remote shell is not that hard (pretty sure there are tools out there that do exactly this).

So what you get: A tiny bit more security but a lot more inconvenience. For example: You can't read the JWT in your app (because it is stored in httpOnly) and use the userId or other information that is coded in the JWT in your app.

Thread Thread
 
jonathanihm profile image
Jonathan

Agree to disagree- it really depends on the application, but when we are talking JUST in the context of pure security JUST from XSS, localStorage is not the best option. Obviously you do need localStorage for things like state management. My issue is just the wording "a bit more" when in reality its "a lot more".

Collapse
 
dezfowler profile image
Derek Fowler

Once you get past the title of this article (which I think really needs to change to something more like "Which is best for a simple website: token or session auth?" but I get the desire to be clickbaity) the quote which I think is the core of this argument is:

This is why Awad (2019) says he uses session tokens, not JWTs, for a simple web app.

and that's pretty reasonable especially if, as the rest of the post suggests, you intend to implement the token auth yourself rather than using a service.

I think a big piece missing from this is that session auth is not a standard and is often a roll-your-own kinda deal where you need a user database with passwords and some session["isLoggedIn"] thing going on but token auth is a standard and the more usual way to implement that is to use a library or an external token service. The advantage here being that token auth is more secure by default and if you use an external service you don't have concerns about secure password storage, refresh tokens, etc someone else already solved that problem.

From that point of view token auth via an external service is actually a better choice for a simple website because you write zero code and zero code means zero bugs and that you can spend that time implementing the features you can't just buy off the shelf.

Collapse
 
leob profile image
leob

The advice that I've always heard is: DON'T implement security yourself! It's far too easy for the average (web) dev "to do it wrong". So, use a good library instead, e.g. Passport.js in the Node.js world to mention a well-known one.

Now, choosing between session tokens or JWT's might not count as "implement your own security", but it gets close. Arguably the average mainstream web dev does not have enough knowledge/insight to make this decision.

So, again, use a library or framework (probably you should use the most popular / most well-supported), and go with what they support, or what they advise (for a given use case).

(note: I am not saying that it isn't a good idea to learn and understand about concepts like session tokens, JWT, XSS, CSRF and so on - it's a great idea to study this stuff and to have a general understanding of it ... but don't reinvent the wheel and implement your own auth or security)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

Arguing that you shouldn't store access tokens in local storage because XSS is akin to arguing you shouldn't store your underwear in wooden drawers in case your house burns down. If you have a XSS vulnerability, you're screwed, and so are your users if you don't notice it quickly and shut everything down.

Local storage as least as long as everything else on your site is safe. If local storage is compromised, your house is on fire.

As for quick expiry: what's the point when an attack can happen in a matter of seconds after a token has been exfiltrated? The moment the access information gets to a malicious server, count to 3 and you've bought ten washing machines delivered to india, rated 30k products with 5 stars and sent an 20 messages with scam links to each of your contacts. Using short-lived tokens is only an option for invalidating old sessions reasonably quickly without hammering the database for every request; but it doesn't add much security beyond this.

If you think you need safer technology for your access tokens, you're probably looking at the wrong place, and should instead consider adding other layers of security: Asking for a password, or at least running much more secure checks on important actions (Buy things, delete things, transfer ownership of things, etc.); Give users an option to invalidate all of their sessions and do this automatically if they change their password, email, or other such information; send out emails for certain actions, if not asking for confirmation, then at least to inform the user and possibly giving them a way to revert whatever changes were made.

Collapse
 
frankszendzielarz profile image
Frank Szendzielarz

From my point of view this is comparing apples to oranges. Presumably OAUTH access tokens could even be implemented as session tokens if you so chose. I think it is brave to make public notes like this while at the early stages of understanding, so I commend you for that. I think there is much to learn on this topic and putting yourself out there like this is a good way to go about it. As they say: fail, but fail fast.

It is also good you added strong caveats that this is a note to yourself, not guidance.

Collapse
 
jexperton profile image
Jonathan Experton • Edited

Given some comments above, it should be clarified that a refresh_token should never be exposed to the client side as it never expires nor changes. It can be stored encrypted in a JWT but it’s an information that should be exposed to trusted components only. It is extremely important especially if you’re using a third party service to emit access_tokens because you probably cannot revoke refresh_tokens.

In a OpenID Connect, which is based on OAuth 2.0, when an access_token is about to expire, the refresh_token is meant to be used by a backend to ask for a new access_token to the third party in charge of emitting tokens without asking for user interaction. It allows short living token and therefore you can make long authentication sessions that expires if no user interactions are made in a short interval.

Collapse
 
gramzivi profile image
Ivan

Authentication, authorization... well security in general, is not a simple thing that can be explained so easily. There is no mention of CSRF and XSS problems with sessions, session hijacking. Also, you said, "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". It's not major, it's the reason why there is no serious modern APP that can completely rely on session tokens (even if you put everything in Redis, or have a separate authentication server). Also, there is no serious application that completely relies on JWT. Some things are better to keep in JWT, some things are better to keep in session. But if you have a website where security is important, you need everything you can get. Don't afraid of using a database or have one additional request every 15 minutes if that will improve security or stability.

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

This is a great write-up.

One criticism / suggestion Re:

Performance suffers consequently, as retrieving data from a database always takes some time

Caching and event-bus are your friends here. Worst case, you'll still be correct, but at scale peak performance has to be a network request anyway, since commodity hardware struggles to presently be able to address hundreds of gigabytes of user data in a short time, resiliently, etc.