Building Secure Node.js Apps with JWT
JSON Web Token (JWT) as an open standard for web security have been around for a while, and there are a lot of varying implementations across different languages. In this article, we’ll be focusing on use cases and implementation options of JSON Web Tokens in the context of a Node.js application.
What’s a JWT?
An acronym for JSON Web Token, a JWT is essentially just a secure and condensed way of passing and verifying dynamic data within/across APIs or servers in JSON format.
So this means a JWT has both the capability of passing data around as well as a built-in mechanism to detect whether that piece of data actually ensued from the claimed source when it eventually arrives at its destination.
Structure of a JWT
A JWT contains three different parts: all encoded in base64 and connected with a period. A modification to any of the parts will result in a JWT verification error.
const HEADER_HASH = base64(header);
const PAYLOAD_HASH = base64(payload);
const SIGNATURE_HASH = base64(signature);
const JWT = HEADER_HASH + '.' + PAYLOAD_HASH + '.' + SIGNATURE_HASH;
//JWT xxxx.yyyy.zzzz
The first part of the JWT is the header which contains information such as what algorithm should be used in securing the data.
The second part is the claims or payload; it contains the data being passed. It's referred to as claims because when the JWT is issued from the client, it’s in essence claiming that such data actually belongs to them.
The last part is the signature, which is essentially a fusion of the header and payload encrypted with the algorithm specified in the header and a secret key we specify.
One more thing: the process of signing the token here doesn’t make it private or unreadable; it could actually still be deciphered. So, passing sensitive information in the payload is a bad idea.
So, what makes JSON Web Tokens relevant in the world of app security?
Perks of JWT
One of the major benefits of using JSON Web Tokens is that authentication and authorization can be abstracted away from your application server and delegated to a third party provider. That means you can save your deployment server some extra overhead, which could lead to better performance and less resource usage.
JSON Web Tokens are stored on the client side so even if you decide to generate the tokens within your Node.js app, your database server would not need to be bothered with token storage because everything is stored on the user’s end. This means less stress and better performance for your app too.
Okay, so we understand some of the benefits of using JWT, but when are they particularly useful?
JSON Web Token (JWT) use cases
Authorization
While building your Node.js app, you’ll definitely want to have exclusive or protected routes which only users with certain rights are allowed to access. So typically, once a user logs in, they are issued a token that will be sent along in any subsequent requests which enables them access routes or resources within their access level. JWT was born for this!
Information Exchange
Often, we need to pass data across routes or to authorize transactions across different servers. In such a use case, JSON Web Tokens are also a good idea.
Let’s use a pretty familiar web development example: We have a dashboard route in our app where we want to display a personalized message like Hey ${username}
.
One way this can be done with JWT in the picture is to have a middleware that decodes and assigns the token generated to a user object. Then in our dashboard route, we import the middleware, grab the username and pass it to be displayed on the front end. Of course, this means we need to have username as part of the information included in the JWT payload.
JSON Web Tokens can also be easily shared across services so this means if you need to synchronize data between web and mobile platforms, you should consider using JWT.
When JWT isn’t the best choice
Agreeably, JWT can be a spice. However, we need to watch out for situations or use cases where we could use them wrongly -- such as using them in the way traditional sessions are used. Here’s why:
In a session-based authentication workflow, the actual piece of user data is stored on the server while the client stores just the reference id. So it makes sense to store varying data including sensitive ones since the data is not exposed.
However, in token-based authentication like JWT, one of the selling points is that it reduces round trips to the server. This is because the token is self-contained which means it stores the actual piece of data, not a reference id.
So given this, we may be tempted to put all kinds of data -- even sensitive ones -- into the token in a quest to simulate a typical session-based workflow. This would be wrong and would mean exposing our app to malicious attack and manipulation. We’d also be defeating the purpose if we were to load the token with too much data.
JSON Web Tokens were not designed to replace sessions but rather to give us more options and flexibility. So it’s important to weigh the core needs for your app before deciding whether to use them. You can check out this visual explanation for further insight on this.
Generating JSON Web Tokens
Assuming we’ve decided (for all the right reasons) to use JSON Web Tokens in our Node.js app, how do we go about generating the tokens?
We’ll talk about 2 ways this can be done:
Via a third party provider
As mentioned earlier, one way to go about generating the tokens is to use a third party provider that handles this for you. For example, Auth0 has a service which you can use server-side to generate tokens for your app.
Once you write the code to plug into their service, the rest is handled for you. You can find out more here.
However, if you want to have more control over how the tokens are generated, consider the next option.
Within your application server
JSON Web Tokens can also be generated and managed within your application server. There’s a variety of libraries and APIs in the Node.js ecosystem that helps us easily achieve this. We’ll focus on working with JWT within our application for the purpose of this article.
Generating tokens with nJWT in Node.js
The njwt npm module is one of such tools that helps us create and verify JSON Web Tokens.
The create
method of this module generates the token using the provided parameters:
nJwt.create(claims,signingKey);
(We’ll need to store our secret key for signing the token in an env file and remember to add to your project's .gitignore.)
The verify
method decodes the token extracting the payload or data; if there’s an error in the process, we handle that with a callback function.
nJwt.verify(token,signingKey,callbackFunc)
To understand better, let’s consider a two-part code example. In the first part, we’ll create the token. In the second part, we’ll verify and decode the token using nJwt.
Creating the token
const nJwt = require('njwt');
require('dotenv').config()
const generateToken = (username, id) => {
const signingKey = process.env.SIGNING_KEY;
const payload = {
username,
id,
scope: 'admin'
}
const jwt = nJwt.create(payload,signingKey);
return jwt.compact();
}
exports.generateToken = generateToken;
In this example, we're working with the njwt and dotenv modules so we need to install those in our app first.
The dotenv module makes the signing key variable in our .env file globally accessible in our project.
return jwt.compact();
The compact
method above gives us a concise token string. If you'd like to take a look at the nitty gritty, you can omit the compact and return jwt
.
This is the first part of the code example which is creating the token. Now let’s take a look at the next part of the piece, verifying the token.
Decoding the token
const generateToken = require('./generateToken');
const nJwt = require('njwt');
require('dotenv').config( )
const token = generateToken('Jane' , 4);
const signingKey = process.env.SIGNING_KEY;
nJwt.verify(token, signingKey, (error, verifiedJwt) => {
error ? console.log(error) : console.log(verifiedJwt.body);
}) ;
We require the generateToken
module we created earlier, and supply the 2 parameters we defined: username
and userId
. The nJwt.verify
function accepts the generated token and the signing key and checks if the token is valid, that is, if it was actually generated from within our app or it’s just some cooked-up fake string.
Take note that we’re using console log for test purposes; you should use a callback function in your actual code base.
So the good thing about JSON Web Tokens is that if it gets tampered with along the way, it can be detected. This makes it trusted much like we do a password.
To prove this, let’s tweak our code to add in some extra strings to the token.
const token = generateToken('Jane' , 4);
const fakeToken = token + ‘gh67j’;
const signingKey = process.env.SIGNING_KEY ;
nJwt.verify(fakeToken , signingKey , (error , verifiedJwt) => {
error ? console.log (error) : console.log(verifiedJwt.body);
}) ;
You should get an error passed to your callback saying the signature verification failed.
Recap
Because of their built-in verification, JSON Web Tokens are a great option to ensure security when used properly. Plus they’re pretty easy to get started with in your Node.js app, and you can take advantage of the many libraries that help you implement JWT.