JSON Web Token – or JWT (pronounced ‘jot’) – is an access token standard used by applications to create signatures of data sent across the web. It can also encrypt payloads on JSON sent, where tokens are either signed using a private or public/private secret key. It is the server that can use the token to verify that the incoming JWT is from a legitimate source.
But where do we use JWT?
JWTs are used in the context of authentication, where a user verifies by, for example — logging in — a token is returned by the server to be saved on the client. The client relays this token every time it sends a request to the server. The server then uses this token to give access to whatever restricted resource is allowed based on the user’s permission levels. This resource is accessible for a set amount of time. Once it expires, the JWT becomes invalid and a new token needs to be issued.
In this article, we will be going over how to use JWT in a Node.js and Express application, advantages, and potential vulnerabilities that JWTs can be exposed to.
How to use JWT in a Node.js Express application
When it comes to Node.js, Express.js is often the go-to framework for creating web and mobile application features. In this portion of the article, we will be going over how to create a JWT token, how to authenticate a Token, and how to handle client-side Tokens.
How to create a JWT Token
To generate a JWT Token, we are going to need jsonwebtoken. To do this, we can install it via npm to your project. Use the following command to achieve this.
Once this is completed, import it into your project. Here is an example of an import.
In order to sign a token, you need 3 parameters –
- the token secret
- the data you want to hash into the token
- how long the token has before it expires (that is, time to live)
To create a token secret, you can use Node.js built-in crypto library. Here is how you can generate a secret using the crypto library using a bash command in your console.
This will generate a secret key for you. You can copy this key and store it in your project’s .env file. It is good practice to ensure that this .env file is ignored by git so you don’t accidentally push it into your repo. Storing secret keys in git is bad practice and there are other options that allow you to safely share keys in a managed way.
If you haven’t already done so, install your .env file so you can import it into your project. Here is the command and code to complete this process.
To import your environment variables into your Express.js application:
It is good to note that TOKEN_SECRET is the name that your secret is set under inside the .env file. In theory, it can be any name you want, as long as it communicates what it does. This TOKEN_SECRET will be used to hash your data. Here is an example code function of how you can generate a JWT access token:
param is the data that’s going to get hashed, process.env.TOKEN_SECRET is the variable used to hash the data, and expiresIn sets the TTL on the generated JWT.
Here’s an example of how you can generate a JWT using the function above and use it as part of your API.
It’s one thing to send a JWT to the client. But how do you check and verify the JWT you get back? To do this, you can use jwt.verify
Here is a code sample of what it looks like:
Advantages of using JWT
By design, HTTP is a stateless protocol. This means that there’s no log of the session, making it hard to identify if a user is who they say they are, or a malicious user. JWT makes identity verification possible in a stateless structure.
However, this structure creates an issue for hybrid, microservices, and serverless solutions. In these setups, there is no centralized way of identifying the validity of a session. Cookies received from the client aren’t ‘remembered’ or recognized by the server because of the way it is structured.
JWT solves this problem because it doesn’t use sessions. Instead, the server sends over a Token that can be unhashed based on the secret key. The centralized and lightweight distribution of decryption across functions lets us get to the information we need quickly.
The major advantages of using JWTs include no session to manage, highly portable where a single token can be used with multiple distributed backends, no cookies required and therefore making it mobile-friendly, and is decoupled and decentralized from the application’s modules.
You can also run JWTs with signatures and encryption to ensure that your Tokens and their data remain safe. It’s good to note that signatures are useful for validating the data against tampering. Encryption prevents others from reading the contents of the Token.
Disadvantages of JWT and How JSON Web Tokens Can Be ‘Hacked’
A signed JWT contains three parts to it: the header, the payload, and the signature. These three parts are encoded separately. This means that a malicious user can remove the signature and change the header to claim that the Token is unsigned. To prevent unsigned Tokens from slipping through, an extra validation is required by the server to reject unsigned JWTs.
In the instance of a Cross-Site Request Forgery (CSRF), a user loads a fake site and invokes a valid target URL with added parameters. This may be done by clicking on a link, image, or form. The client sends requests with cookies. The server sees it as a valid request and allows the malicious user to obtain whatever they are looking for.
The issue here is that JWTs need to be stored somewhere and the default space for some developers is in cookies. The way to mitigate CSRF is to not store Tokens in local storage (also known as session storage) but in the browser’s memory instead.
The way to fix this is to do proper validation of all data based on the backend. Data received from the client should be sanitized by default. If cookies are used, they can be protected by putting an HttpOnly flag on them. However, it’s good to note that the HttpOnly flag will not protect against CSRF attacks.
JWTs are fairly safe to use — when implemented correctly. Many tutorials online tend to suggest that you save JWTs in cookie sessions. While it is easy, it is one of the most vulnerable spaces you can store JWTs. As mentioned above, storing it in the browser’s memory is the safest location. However, it comes with its own disadvantages such as if the user refreshes, then they might lose the session. This mostly applies to single-page applications (SPAs). Server-generated pages may have a different impact, but that is beyond the scope of this piece.
JWTs are available to other languages and are often supported by libraries and frameworks such as PyJWT for Python, JWT-CPP for C/C++, and JWT Token that is available as built-in functions in Java. In short, JWT and its usage are flexible and universal.
Overall, when used properly, JWTs can easily allow for serverless architectures and microservices to be implemented with ease. It gets rid of the need to create ‘sessions’ in the traditional sense and decentralizes the process through a centralized hashing private key.