DEV Community

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

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!