Integrate JWT to your Authentication system within 10 minutes

By building a Referral System using Node.js, MongoDB and Express

Photo by Jefferson Santos on Unsplash

Welcome! In this article we will be learning how to manage user sessions using JSON Web Tokens (JWT). At the end we will also build a simple referral system to demonstrate the workflow with JWT.

If you already have a user authentication system, you can refer to this article to integrate JSON web tokens securely in your existing project.

Without wasting more time on the introduction let us get started.

This post is the continuation(Part II) of my previous article, in which I have shown the implementation of user signup with email verification, forgot password and reset password features. If you want to take a look at Part I, use this link.

Let us have a look at our Login API.

Here, we can see that, once the user has been logged in we just return the success flag and a message which is not sufficient for creating a valid session. Now let us create an access token (valid for 1 hour) for every login and send it to the client.

What is JWT ?

A JWT is just a session data payload JSON format that is cryptographically signed by the server.

More detailed explanation:

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Generating the token

Install the “jsonwebtoken” module using the command:

npm i jsonwebtoken --save

Inside the users/helpers folder, create a file generateJwt.js

Add your JWT Secret in the .env file

JWT_SECRET = <SOME_LONG_SECRET_KEY>

This JWT Secret will be used as the key for signing the payload.

Save the files and start the server.

Note: The longer the secret is, the safer will be our application. As we are using HMAC algorithm(symmetric), a single key is enough for signing. If we use RSA, then we need to have 2 keys (private and public).

Update the user schema to store the accessToken.

accessToken: { type: String, default: null }

Let’s import the generateJwt function in user.controller.js file and use it to create an access token.

Yes, we are saving the Access Token in our DB. Many of you may think that it is not the proper way of using JWT as the main use of JWT is to facilitate stateless authentication, which means we need not store information in the server. Because JWT itself contains information like expiration time, payload, issuer, etc., But my goal is to create a **secure **API using JWT and I mean it.

Note: JWT is not secure by itself. What makes it secure is how we use it within our system. I’m not willing to take this article out of the path as the objective of this post is to demonstrate the integration of JWT with our authentication system.

For those who are still interested why do I store the access token in DB, what I am doing is whitelisting the token. We must either whitelist or blacklist the token in order to improve the security of the underlying system. The most preferred way is blacklisting, the search space will be much lesser when compared to whitelisting.

PS: Of course, I’m not a security expert. So if I am wrong in terms of security please let me know. I’ll be happy to learn.

Now let us login from the Postman:

Cool! We got the access token which is valid for 1 hour. We will not get the same token every time. Each time a different token will be generated for everyone.

JWT Gotcha

Another misconception about the JSON web tokens is that the token is encrypted and all information stored is completely safe. We must understand that our payload is “signed” it is not “encrypted”.

Let’s do some experimentation.

This the access token that I recieved in the response:

Open a new tab in your browser, visit http://calebb.net/ and paste the token.

You’ll see something like this

Yes, it is possible to retrieve the JSON payload which we store inside the JWT by decoding without the key. **The secret key is required only for verifying whether the JWT is valid.**Hence we should never store sensitive information like passwords, API keys, etc in the JWT payload.

Once again I emphasize that JWT is not secure by itself. It depends upon how we use it.

Implementing a Simple Referral System

To understand how to implement secure endpoints, we will create a simple referral system.

Here’s how it works:

  • Generate a referral code for the user during registration and save the code in DB.

  • While signup, get a referral code from the user.

  • Check whether that referral code exists in the DB.

  • If it exists then store the details, else throw an error.

For generating the referral code, we will use a short unique Id generator “nanoid”.

Install the dependency: npm i nanoid --save

We will be using a custom character set that will include the characters A-Z, a-z, and 0–9 to generate referral code.

Also, add the following fields to the user schema.

referralCode: { type: String, unique: true }, referrer: { type: String, default: null }

After making all the changes, our user model will look like this:

Now save the file and restart the server.

Let us signup and check whether everything if working fine as expected.

Cool! We got our referral code RiNb08qd . Let us take a look at our collection in DB.

We can see that the referralCode has been added to the user collection.

Let’s assume that we have shared this referral code with some of our friends. So he/she enters our referral code during registration.

It works! Our friend was able to sign up with our referral code.

Now let us add some more functionality to our project. Say, that we need to retrieve all the accounts that have been registered with our referral code, and when we log in, it must be displayed on our dashboard.

The flow

  • After successful login, store the JWT on the client-side (in local storage, indexed DB or so).

  • Then call the GET /referred endpoint with the access token in the authorization header.

  • Verify whether the token is valid, find the user-id by decoding the user-id from the JWT payload.

  • Using the user id fetch the referral code, then find all the collections in which the referrer field matches the referralCode.

But how do we validate the JWT passed in the header? Simple, we need to call the verify API that is present in the jsonwebtoken module, as we used the sign API. Let’s go ahead and implement the middleware that is used to validate the access token.

In the middlewares folder, create a file called validateToken.js

This is a more secure way of validating a JWT token. Rather than just verifying the token ,we are checking against our whitelist (in the DB).

Now let’s define our controller to fetch all the referred accounts.

Then we must add the endpoint to our router. In the routes/users.js file, import the validateToken middleware and use it in the newly created endpoint.

const { validateToken } = require(“../middlewares/validateToken”); const AuthController = require(“../src/users/user.controller”); router.post(“/signup”, cleanBody, AuthController.Signup);

...
...

router.get(“/referred”, validateToken, AuthController.ReferredAccounts);

After updating the files, let us test our endpoint.

Our middleware throws the error Access token is missing. Let test the by passing a random token as the header.

It works! Now let us login to obtain a valid access token.

Now let us pass this access token in the Authorization header as the Bearer token.

Boom! We got all the accounts who have registered using our referral code. Hence using the JSON Web Tokens we can secure as many endpoints as we need as we did for the GET /referred endpoint.

Now, let us define the controller and endpoint to logout the user. User logout controller is straightforward. We need to just clear the access token from the DB that’s it.

It’s also a protected endpoint, only the logged in users with a valid access token should be able to logout.

After adding the logout endpoint to our routes/users.js file, it will look like this.

Finally let us check the logout endpoint.

Let’s try to fetch the referred accounts using the same token.

Great! It works as expected. If you check the user’s collection, the access token would have been cleared from the user document. Hence, we have successfully (and securely) integrated JWT into our authentication system.

You can find the entire source code in this GitHub repository.

Conclusion

In this era of the modern web, there is nothing called “this is the correct method” to implement a feature. It varies from system to system based on the requirements. So let us explore and learn something new every single day!

If you have any doubts or queries about this post, I’m always open to discussion.

Thanks for reading!