Role-based Authentication in Node Js
Tags: javascript, node, webdev, tutorial
Published: 2022-08-15 11:19:53

**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:

*   **name**(for the name of URL like “set-username”)
*   **path**(for base path hit “/users/”)
*   **url**(requested URL or complete path “/users/set-username”)

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:-

1.  **Wanderer** (*Someone who is just visiting our site. He should be able to access all public routes. Simple URLs/Public URLs accessible to all thus need not make a separate role for this as it is not any authenticated right.*)
2.  **Guest** (*Someone who has registered but not verified say email not verified*).
3.  **User** (*Someone who has his verified email*)
4.  **Admin** (*Made an Admin by SuperAdmin after verifying.he enjoy most of the rights*)
5.  **Superadmin** (*Master of application.He enjoy some more sophisticated rights.More rights then admin*)

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:

```js
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

```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:

1.  Load All simple URLs(public) and Auth Urls(admin & users) & super-admin-specific URLs into superAdminRights\[\].
2.  Then run a function to create a user with role superadmin if doesn’t exist.
3.  Get a Role of type:”superadmin” if found:replace its rights with new rights(superAdminRights).else:create Role of type :”superadmin” and then fill its rights(superAdminRights).

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:


```js
// 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;
}
```