Custom Role Based Auth Mechanism For NodeJS

Atif Hossain
Gen-Y
Published in
7 min readNov 1, 2021

Introduction

Having a proper well thought out Auth mechanism is a fundamental requirement for your REST APIs. By Auth, I mean both Authentication and Authorisation. Although very similar, the two words have different meanings. In this tutorial, we are going to build an authentication (login) & authorisation (using JWT) system for NodeJS application.

Pre-Requisites for this tutorial

  • Nodejs
  • Express
  • Any Database

If you are worried about the security of your node app , I assume you already know your way around Node, Express and a Database. I’ll be using MongoDB in this demo but feel free to use whatever you would like to.

The Project

Here’s what we will build in this tutorial.

  • Create user API
  • Login (Authentication) API
  • Authorisation Middleware
  • Get User Profile API
  • User status update API (Admin only)

Set Up

Let’s setup the Project real quick. We’ll be using the following dependencies;

  • express
  • mongodb
  • jsonwebtoken (implementation of JWT)
  • bcrypt (password hashing)

Run the following commands , or use YARN or use GUI or something. I have installed some other dependencies like nodemon as well but these four are the only packages you must need.

mkdir node-auth
cd node-auth
npm init --yes
npm install express jwt mongodb jsonwebtoken bcrypt

JWT

Back in the old days , once the user logged in they were assigned a session id which would be stored in a cookie in the browser. Every subsequent request henceforth would carry the cookie which the server would compare with the stored session id in the database to confirm the user’s identity. For each user, server needed to store the session id (along with other information) in the memory and then each request had to first query the database for session id before proceeding. You can probably imagine how scaling can become an issue if there are a huge number of users using the system at once.

So developers came up with another solution to this conundrum. JSON Web Tokens (JWT).

A JWT is essentially a JSON object that contains information regarding the user , which servers can then use to decide whether the particular user has access to the resource(s) asked. Instead of storing session data on the server , the JWT is stored only on the client. The token in itself is enough to verify the claims of the user. That being said, it does not mean that JWT is without cons of it’s own. Just like any other thing, using JWT has certain disadvantages which we shall not dive deep into here.

A JWT is made of three parts : Header, Payload And Signature. Payload contains whatever information we want to store in the JWT that we can use for authorising.

To give you an overall idea, here’s how the JWT system works :

  1. Client sends the login credentials to the server.
  2. Once the server Authenticates the user , it will generate a signed token (using a secret key) which will be sent to the client.
  3. The client stores the token and all subsequent protected routes must be called with the token in the request header.
  4. Server validates the token and if valid , proceeds to serving the request. If the token is not valid (broken, expired etc), unauthorised response can be sent back to the client.

A secret token (any string) is used to sign the JWT and this string should only be stored on your server. This string is like the master key for the tokens. It must be protected at all costs.

Folder Structure

I’m all for proper folder structure (helpers, config , middle-wares … ), but for the sake of simplicity, lets just create the following files in the root directory and get going. The folder looks something like this.

- app.js
- auth.js
- route.js
- db.js
- package-lock.json
- package.json

Data Model

Our database has just one collection ‘users’ with the following schema. . This is just a minimal representation of a user in our system.

{
_id: ObjectId(),
name: String,
emailId: String,
password: String,
role: String
isActive: Boolean
}

Each user is assigned a particular role and this will be used to limit access to certain APIs. Let’s assume there are two kinds of users in our system, administrator and ‘normal user’ with limited access. The roles assigned to these are ‘admin’ and ‘user’.

Note : ‘isActive’ field can only be updated by a user with admin access. You can use this to restrict login for certain users by setting isActive as false for that particular user, I have done no such thing here. It’s here just to demonstrate the admin API.

The Code

App.js

We’ve initialised the server , used the required middle-wares and connected with mongodb. The mongo connection was established once and then exported from db.js. You can find the full code in the github repo which is linked at the end of this article.

The Authorisation Middleware (auth.js)

There are three important pieces of code here

  1. function issueToken(user)
  2. function authorize(roles)
  3. variable Roles
  • The function issueToken() is used to create a token given the payload (user information like userId, emailId, role etc). We define when the token will expire (after which token will not be valid)
  • We’ll use the authorize() function as middleware for protecting routes. It takes an input ‘roles’ which is an array of strings containing the roles for which the particular route will be accessible. This is what happens :
  1. We first make sure ‘roles’ is an array.
  2. Then we make sure the token exists and it’s in the right format (Bearer {token}).
  3. Next we decode the token and verify the validity of the token and its expiry time.
  4. And finally we check if the role of the current user (decoded from the token) is present in the list of roles provided for the route.
  5. The decoded token is then stored so that we can access the user details from req.user within the APIs rather than decoding again.
  6. If any one of the above conditions don’t match,403 Forbidden response is sent back to the client with appropriate error message.
  • The variables Roles will be used to pass parameter to the authorize function. You could very well pass pass an array literal of roles if you want to. But I’m being fancy here , so bear with me.

Routes

Lets make the POST /user route for creating a new user.

Routes.js (I) — Create User API

Taking the user details and hashing the password through bcrypt , the user information is stored in the database. We returned a token as soon as the new user is created so that the user does not have to log in after creating a new account (or do a silent login on the frontend).

Routes.js (II) — User Login API (Authentication)

Authentication can be defined as the process of verifying the identity of the user. We check if the user is actually who they are posing to be. This is done through the login API. We check if the email id given exists in our database and then compare the stored hashed password with given password. If the user is authenticated, a json web token is generated using the user details (including their role) and sent back to client.

Standard procedure here.

  1. Check if given email exist in our database.
  2. If user found, check if the password given matches with password stored.
  3. If both email and and password is correct, issue a token. If either or both are wrong, send appropriate response.

Routes.js (III) — User Profile API

Here’s the juicy bit. Let’s create an API for getting the user profile. This API should not be accessible by any client who has not logged in (meaning has no token). We’ll verify the token and use the information decoded from the token (stored in req.user) to get the user id for the query.

  1. The request first passes through the authorize() middleware. Since the get profile API should be accessible to every user that exists in our system , we have passed Roles.All ([‘admin’ , ‘user’]) to the function. We did not use the ‘authorize’ middleware in POST /user because no authorisation is required and anyone should be able to create a new user.
  2. Once the logged in user is authorised , their details can be fetched from the database and sent back.

To show another example of the auth middleware, let’s create another API which will update the isActive field of the user. This action can only be performed by an admin and not by a normal user. We have passed ‘Roles.Admin’ here instead of ‘Roles.All’.

Routes.js (IV) — User Status Update API (Admin Only)

If a user with role ‘user’ tries to access this API , they will get a 403 Forbidden error thown in their face.

Conclusion

This was a simple application of Authenticating and Authorising on NodeJs. It can be extended to add more rules for authorising and can be crafted for your particular use case. We used JWT to simply sign the payload and ensure the integrity of the token. What that means is anyone with the token can see the payload but any modification of the payload will invalidate the token. To provide more security , a token can be encrypted after signing it as well.

Hope this was helpful and … have fun coding.

Here’s the link to the whole source code

--

--

Atif Hossain
Gen-Y
Editor for

A javascript developer by profession, and by heart. I’ve learned & honed my craft from the internet and trying to give back to the community through my writing.