JWT authentication, The right way.

JWT authentication, The right way.

·

6 min read

I've seen a lot of articles talking about the famous JWTs (JSON web tokens) and how to use them in your software application, but they don't really talk about how to secure them from popular web attacks like XSS (cross site scripting) or even the popular CSRF (cross site request forgery) attacks.

Table Of contents

  • What are JWTs
  • Common Attacks you can do with JWTs
  • Cross Site Request Forgery
  • How to prevent CSRF
  • Cross Site Scripting (XSS)
  • How to prevent XSS
  • Other ways to keep your app secured

    What are JSON Web Tokens?

    JSON Web Tokens (JWT) is a JSON-encoded representation of a claim or claims that can be transferred between two parties. When a server receives a JWT, it can guarantee the data it contains can be trusted because it’s signed by the source. No middleman can modify a JWT once it’s sent, but that doesn’t mean it can’t be stolen.

This was actually a big issue for me while venturing into software development. I first learnt about JWTs how it solved a lot of problems is scaling web apps. Everything was going well till I realized a couple of exploits. These vulnerabilities are often noticed if you're trying to keep your users authentication for a long period of time and not just for a session. In this article, I'm going to go about these vulnerabilities one by one and the right way to counter them making your web application more secured.

I’m not going to cover JWT from scratch, if you have little or no understanding about JWTs, check out How to implement JWT Authentication with Node Js. Let's get started shall we?

1) Cross Site Request Forgery (CSRF)

This kind of exploit enables hackers send authorized request to the server through the users device.

Let me give an example of this exploit, Server A (”api.server-a.com/reset”) which is a backend API to reset a user's password on “server-a.com”. Now with every request that's sent from the client, a cookie containing the users id is sent along so the server could easily identify the user that is making that request. The problem with this is that if the backend API doesn't set a limit to which frontend application is allowed to send in requests, hackers can easily exploit this.

Let's take it this way, a hacker can build a website with a hidden form which is set to submit to “api.server-a.com/reset”. The hidden form would also contain an input tag with a default value as “123456” which he intends to use as users new password. Once he's done creating this form and he hosts it, he would wait for the user to be logged in officially to “server-a.com”.

Now this would be difficult if it's a website which you would always have to enter your credentials every time you close and open the website. But what if it's a website like Facebook which tries to keep you logged in for a long period of time using cookies😏.

You can see how this attack gains ground over your application, once the actual user enters his login credentials and logs in the first time, the attacker now sends him the link to his own html form. The user can click it and either he gets redirected to a blank or a dummy page entirely. He wouldn't know that as he clicked that link and opens that dummy page, the hidden form has already been submitted using JavaScript which sends a request to the endpoint “api.server-a.com/reset” which the server would authenticate because as long as a request from the users browser is sent to that endpoint, the cookie is also sent regardless of the frontend application the request was sent from. So the request is sent and the attacker has conveniently tricked the user into resetting his password.

How to avoid:

  • Set up a strict CORS policy to accept request only on specific sites (if possible, use the sameSite attribute or allow only specific domains with the origin option if your building an API).
  • Set up a short lifespan for your web tokens with an algorithm to replace the token once it expires. You can use a Refresh token to implement that.
    cors({
      origin: process.env.CLIENT_URL,
      methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
      credentials: true,
    })
  );

CLIENT_URL = 'http://localhost:3000'

Cross Site Scripting (XSS)

This form of exploit is completely different from the CSRF exploit I mentioned above. This attack usually if your JWT (JSON Web Token) is stored in a place that can be accessed using javaScript like local storage or a regular cookie. Using this exploit, the attacker can easily steal these JWTs from your browser and place on his own system. Immediately he does that, he would be automatically logged in to your account(for a limited time before the token expires) without the need to put in your credentials (email and password).

This is a very common attack in the software space and I’ve seen a lot of developers storing some user information inside their JWT tokens, remember that JWT tokens are not encrypted but rather encoded with base64 algorithm and these values can easily be read by someone who can decode base64 or a base64 decoder tool.

Note: Do not store sensitive information like passwords inside the JWT. You can store less sensitive information like their username or userId to easily identify them when a request is made.

Untitled.png

Reading cookie value from the browser console

How to avoid XSS

  • Store your JWT in httpOnly cookies to prevent javaScript from reading them.
  • Do not store sensitive information in JWTs
  • Only set cookies over a https network.

Note: using httpOnly cookies still don’t prevent your JWT from getting stolen or read, you still need to employ some additional logic of your own like prompting users to enter their login credentials everytime a new device is making an API request.

Other ways you can keep your App secure

  • Store your secret key in an .env file to avoid publishing it to git or any open source environment. Remember to add your .env file to your gitignore.
  • Do not use a simple phrase to generate your secret key. for Node users, check out crypto library for generating hashes for your key.
  • Remember to validate your forms and also use prepared statements when querying the database.
  • Store minimal information in your token. Remember, Base64 is not an encryption.

I think we’re done here, Hope I solved the problem you were looking for. Don’t forget to leave a comment to encourage me write more, you can also contact me directly if you need further assistance.

Your favourite software developer, Kudos!