Role-based Authentication in Node Js

I’m a software ninja 🥷 who loves abstractions and spread functional programming everywhere. I've over 2 years of experience and I'm based in Delhi, India🇮🇳
Search for a command to run...

I’m a software ninja 🥷 who loves abstractions and spread functional programming everywhere. I've over 2 years of experience and I'm based in Delhi, India🇮🇳
No comments yet. Be the first to comment.
Building a Layered Component Architecture with MUI
Layered Architecture Guidelines

When you are building a text editor for general-purpose writing, sooner or later you will need to support emoji’s for your users. In this blog post, we will learn to create an emoji picker for your tiptap-based editor. Let’s define the EmojiData Mod...

State management is always a problem to solve in big applications. Many design patterns and libraries emerge to solve some issues by proposing different ways of doing things. In this article, our main focus is on the philosophy of managing your state...

Availability and reliability are important parts of systems; if you are exposing an API, it should be available and reliable to consumers. You must have experienced this when you grew your API endpoints and received more traffic. It is okay as the bu...

Part 1:What is role and rights?
Role rights implementation is an important part of any software. Role is a position of responsibility and every responsibility enjoys some rights given to them. There may be some common rights between a few roles and some rights may strictly belong to a specific role.
Rights are Urls that a role is authorized to access. It is thus necessary to create a collection in DB storing information of rights to a role. We have role collection schema as
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const RoleSchema = new Schema({
roleId:{
type:String,
unique:true,
required:[true,"Role Id required"]
},
type:{
type:String,
unique:true,
required:[true,"Role type is required"]
},
rights:[{
name: String,
path: String,
url: String
}]
});
module.exports = Role = mongoose.model('role',RoleSchema);
Now, remember every role that is supposed to exist is in Role collection and of above schema type.
In schema rights array of the object we see the object has keys:
Thus if a user with role user has right to change username then he can hit URL /users/set-username.However a wanderer will not be able to access this url. A higher role like admin & superadmin should logically have access to all lower role rights(URLs).
Role in real application are:-
Till now we have understood what exactly is right and how it is mapped to a role.
Part 1.5: Registered Urls/Config Urls
Here we have a file called registeredUrls.js which is like:
module.exports = {
// globally accessible
simple: {
"/":[""],
'/users/':["login","register"],
},
auth:{
//admin level enpoint
'/admin/': ['load-users' , 'set-new-password', 'delete-user'],
'/teacher/': ["add-teacher", "delete-teacher", "edit-teacher"],
'/student/': [
"add-student",
"delete-student",
"edit-student",
"test-result"
],
// common user enpoint
'/test/': ["view-test", "submit-test"],
'/profile/': [
'change-username',
'update-profile-data',
'set-new-password',
'upload-pic',
'update-social-links'
],
'/teacher/':['load-teacher'],
'/student/':['load-student']
}
Similarly confgUrls.js
const configUrls= {
'/roles/': [
'get-rights',
'create',
'update-rights',
'load',
'delete',
'assign'
]
}
module.exports = configUrls;
Part 2:Creating SuperAdmin
This is the most essential part of the application. Whenever the server starts for the first time or restarts/reboots this step occurs. In config/init.js follow the procedure:
At the end of this function call, we are always sure that we have a superadmin in application with all its sophisticated URLs/rights initialized.
Part 3:Super-Admin-Specific-URLs
These are rights that are enjoyed by super admin only and must be maintained in a separate file in parallel to the registered URL file. These include URL rights which map routes used only by superadmin. Here we have routes to create role, load roles, get-rights for a roleId, update-rights for roleId/role type, assign-role to a user, delete a role.
For each user in code, we need to change their role from guest to the user(say after email verification). Or guest/user to admin by superadmin using assign-role URL. Then updating admin rights using route update-rights.
The process ensures that every role has A collection Document and filled rights there.
Part 4:Authenticator Middleware
This heart of our RBACS logic. Here we use a middleware which follows the process:
// get all the URLs/endpoints in the system
const URLS = require("./registeredUrls");
// special endpoints
const CONFIG_URLS = require("./configUrls");
// create array of all endpoints and separate them by auth flows
// simple urls doesn't need any auth flow
const SIMPLE_URLS = [];
// AUTH_URL and SUPER_URL need auth flow
const AUTH_URLS = [];
const SUPER_URLS = [];
// the data-structure is { [rootURL]: [...subURLs..] }
// traverse all registered paths of simple URLs
// and make complete paths i.e `rootURL/subURL`
for (const rootURL in URLS.simple) {
const subURLs = URLS.simple[rootURL];
for (const subURL of subURLs) {
// register all these in SIMPLE_URLS
SIMPLE_URLS.push([rootURL, subURL].join("/"));
}
}
// same with AUTH...register as AUTH_URLS
for (const rootURL in URLS.auth) {
const subURLs = URLS.auth[rootURL];
for (const subURL of subURLs) {
AUTH_URLS.push([rootURL, subURL].join("/"));
}
}
// same with CONFIG_URLS...register as SUPER_URLS
for (const rootURL in CONFIG_URLS) {
const subURLs = CONFIG_URLS[rootURL];
for (const subURL of subURLs) {
SUPER_URLS.push([rootURL, subURL].join("/"));
// push super URLS in AUTH_URLS also as they need auth flow
AUTH_URLS.push([rootURL, subURL].join("/"));
}
}
// By now we have an array of URLs
// 1. Simple URLs don't need authentication flow SIMPLE_URLS
// 2. Authentication required URLs need auth-token
// 3. Config URLs are the highest roles URLs typically super admin
// and have the same flow as Auth URL
// in the node.js middleware callback
const middleware = (req, res, next) => {
// URL or endpoint or path requested
const reqURL = req.url;
// check where URL is
const isAuthURL = AUTH_URLS.includes(reqURL);
const isSimpleURL = SIMPLE_URLS.includes(reqURL);
// auth URLs need auth flows
if (isAuthURL) {
// get token from header
const token = getToken(req);
// validate
const isValidJWTToken = validateJWT(token);
if (!token || !isValidJWTToken) {
// send failed authentication response
// !token missing token required login
// !isValidJWTToken token is invalid or expired
return;
}
// token is valid but we have to check if the session exists in DB
const user_session = getUserSessionData(token);
// If no session in DB means this token may be mischievously generated
if (!user_session) {
// the user token might be compromised
return;
}
// now check if user_session.rights [] has requested URL
// if requested URL is assigned is in array means
// it is assigned that right/path
const hasRightToPath = user_session.rights.includes(reqURL);
if (!hasRightToPath) {
// user doesn't have access to this path
return;
}
// user has right to this path/endpoint/URL so let them do the thing
return next();
}
if (isSimpleURL) {
return next(); // simple URL doesn't need any flow simply pass it down
}
// if it matched none means it isn't a registered path in the system
return;
}