Beginners Guide to Managing User Roles and Pemissions in Node.js
Published: 2024-01-05 09:35:53

As your application grows, managing user roles becomes increasingly important for ensuring proper access control and security. In this article, we'll look at managing user roles and permissions in your Node.js application. 

## Understanding User Roles

Before diving into the technical implementation, it's important to understand user roles and why they are necessary. A user role defines a set of permissions or privileges that a user has within an application. For example, in a blogging platform, a user with the " author " role may be able to create and edit their posts but not publish them. 

A user with the role of "editor" may be able to create, edit, and publish posts but not delete them. A user with the " admin " role may have complete control over all aspects of the application, including managing users and roles.

## Role-Based Access Control (RBAC)

Role-Based Access Control (RBAC), also known as role-based security, is a method of access control that assigns permissions to users based on their role within an organization. It provides a manageable, less error-prone approach to access management than individually assigning permissions, ensuring that users can only access information and perform actions necessary for their jobs. This concept is known as the principle of least privilege.

## How RBAC Works

RBAC works by associating roles with specific permissions and then assigning these roles to users. A role represents a job function within an organization, and each role is associated with a set of permissions that define the operations that can be performed by a user assigned to the role. For instance, you might have roles such as "administrator," "manager," or "employee," each with different permissions. When a user is assigned a role, they gain the permissions associated with it. 

## Implementing User Roles in Node.js

Let's see how we can implement and manage user roles with an example. To get started, I'll provide a basic boilerplate code, which you can get on [Github](https://github.com/oyedeletemitope/template-for-roles-permission) to follow through this tutorial. So, I'll be explaining the files:

- **Data.js:** This will be taking the place of a database. It contains all the different permission roles in our system. The same thing goes for tasks, with each user having their own tasks.

- **Server.js:** here, we are just starting up an express server and importing all the required dependencies and files that we need, telling it to use JSON so we can pull in data from a post request. It also consists of a few basic routes, i.e., the get routes for the homage, dashboard routes, etc. Finally, we have middleware, which sets our user.
routes/tasks.js: contains routes, i.e., get routes to get all the tasks, a single task, and another route to task for our get route.

Once we've cloned the repo, run:

```bash 
npm i 
```

This is to install the necessary dependencies. We also want to install nodemon too . Run:

```bash
npm install nodemon
```
 and then start up the development server by running this command below:

```bash
npm run dev
```

To make an API request, I'll be using [Rest Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) in vs. code. You could use Postman, Thuderclient, or whichever one you prefer. To use the rest client, install from the VsCode extension market. Once installed, create a file called `test.rest` for testing out our routes.

Now, we have a system with different routes, but none of these routes are protected. For example, anyone could access the homepage, but the admin page is also accessible. If we send in a request, we can see that even though we are not logged in, we still have access to the admin page.

We need to make sure that we lock down these different pages based on the role, the tasks they have access to, and whether or not the user is logged in. 

Before we begin, let's import everything we'll need inside our `server.js.` So we won't have to go back again to import them every time we want to work on our `server.js` during the tutorial process:

```JavaScript
const { ROLE, users } = require("./data");
const { authUser, authRole } = require("./basicAuth");
```

Now, we can proceed with the tutorial.

So, let's start with the most basic form of authentication, and that is whether or not the user is logged in. We want to write a form of middleware that will take in the user, check if the user exists, continue, or otherwise send an error message.

Inside the routes folder, create a file called `basicAuth.js`. This will be for our basic authentication. Create a function called `authUser()` like so:

```JavaScript
function authUser(req, res, next) {
 if (req.user == null) {
   res.status(403);
   return res.send("you need to sign in ");
 }
 next();
}
```

We create a function called `authUser(),` a middleware that takes in a request, response, and next. This will check if the user exists and if they are logged in, and if they are, continue; otherwise, it will be an error. Head over to `server.js` and  add the `authUser` to our dashboard routes. Like so: 

```JavaScript
app.get("/dashboard", authUser, (req, res) => {
 res.send("Dashboard Page");
});
```

Testing it out by sending a request. If we send a request with the `userId` of 1, you'll notice we get a response taking us to the dashboard:

![dashboard authentication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a78ctt5ajr518bqu9u02.png)

If we don't have a user, it'll tell us to sign in:

![user has to sign in to access the dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwb19cod7vfhlt91a776.png)

 So, we now have a basic form of authentication, i.e only logged-in users can access the dashboard, leaving only the homepage accessible to all. 

The next form of authentication is for our admin page because we only want our admins to have access because right now, every user and non-logged-in user can access it. The first thing we need to do is check if they are a user and then if they are an admin for them to access the admin page. Inside our server.js, add the `authUser` to our admin route, making it look like so:

```JavaScript
app.get("/admin", authUser,(req, res) => {
 res.send("Admin Page");
});
module.exports = { authUser };
```

To authenticate this to a specific role, we need to write some codes in our `basicAuth` to achieve that. So, inside our `basicAuth.js`, create a function that takes in the role that we want to authenticate, and inside that function, we need to return a middleware, which is the req, res, and next:

```JavaScript
function authRole(role) {
 return (req, res, next) => {
   if (req.user.role !== role) {
     res.status(401);
     return res.send("not allowed");
   }
   next();
 };
```

In the code, we have a middleware function that checks the role of the user making a request. The middleware takes three parameters: req (request), res (response), and next (a function that passes control to the next middleware or route handler).

If the user's role does not match the specified role, the middleware sets the `HTTP` status code to `401` (Unauthorized) and returns a response stating "Not allowed." This restricts access to certain routes or actions based on user roles. Let's not forget to export the `Authrole` too:

```JavaScript
module.exports = { authUser, authRole };
```
Remember, we already imported everything we'll need in our `server.js` before the start of this tutorial. So, in our `server.js`, add the function to the admin route like so:

```JavaScript
app.get("/admin", authUser, authRole(ROLE.ADMIN), (req, res) => {
 res.send("Admin Page");
});
```

So first, we are authenticating a user and then authenticating that a user has the role of admin, and if both are true, then we run the routes that are accessible. So, let's test our admin route:

For `user 1`:


![admin having only the access to the database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn24nvmeavfg5i1lvxjm.png)

For `user 2 or 3`:


![user 2 not authenticated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apvzafi5d679a8ck1b2z.png)


You'll notice that only admins can now access the route. Now, this should cover most of the authentication needs, but we need to be a little more specific when it comes to specific routes, such as task routes.

Recall that in our data, each user has a specific task that they are linked to, except the admin, who can access every user's task. So we need to set up that proper authentication so as not to let any user have access to tasks not linked.

Create a new folder called _permissions_. This is where we'll create permissions for our specific routes. Inside the folder, create a new file called `tasks.js`. First, we'll create a function that checkers if a user can have access to a task:

```JavaScript
const { ROLE } = require("../data");
function canViewTask(user, task) {
 return user.role === ROLE.ADMIN || task.userId == user.id;
}
module.exports = {
 canViewTask,
};
```

In the code above, the `canViewTask` function checks whether a given user has the authority to view a specific task. It returns `true` if the user's role is "ADMIN" or if the task's user matches the user's ID ( if a user is part of the tasks). In simple terms, only an admin (`ROLE.ADMIN`) can view any task, while a regular user can only view their own tasks.

Inside our `tasks.js`, We need to set up authentication around it. We first want to authenticate that the user exists; we only want people to access this page if they are signed in. Let's import the `userAuth` function into our `task.js`:

```JavaScript
const { authUser } = require("../basicAuth");
```

Then we add it to the task route:

```JavaScript
router.get("/:taskId", setTask, authUser, authGetTask, (req, res) => {
 res.json(req.task);
});
```

Notice the `authGetTask`? Don't worry; we'll create that shortly. Then, we want to bring in our authentication for our different permissions for our `canviewTask`:

```JavaScript
const {canViewTask,} = require("../permissions/taskPermission");
Let's now create the authGetTask function:
function authGetTask(req, res, next) {
 if (!canViewTask(req.user, req.task)) {
   res.status(401);
   return res.send("not allowed ");
 }
 next();
}
```

The code above is a middleware that checks if a user is authorized to view a specific task by invoking the `canViewTask` function. If the user is not authorized, it sends a "not allowed" response with a status code of `401`. Otherwise, the request can proceed to the next middleware or route handler.

Let's test it out to see if the authentication works.

You'll see that `user 3` shows "not allowed" when we use it to access a task that's not linked to it:


![user 3 is not allowed to view the task](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqsqrtuglacbp2zztutz.png)

 `user 2` and `user 1` can access it because `user 2` is linked to it and user 1 is the admin:

![user 2 can view the task as it is linked to it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5soubmk85nt6uusb204x.png)

![user 1 can view the task as it is the admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aj2heha9m89b2k19r1iy.png)

The next thing we want to work on is the task route. This is going to return all of the tasks, but now, all users can see all the tasks displayed at once, which should not be so.

The admin should only be the one allowed to see the full list of the tasks, while the user should only get to view their task list alone, so we need to create yet another function inside our task `permissions.js` to handle which actual task the user has access to view. 

So, let's go ahead and create a function called `scopedProjects` like so:

```JavaScript
function scopedTasks(user, tasks) {
 if (user.role === ROLE.ADMIN) return tasks;
 return tasks.filter((task) => task.userId === user.id);
}
```

The code above filters and returns a subset of tasks based on the user's role. It is an example of a role-based access control system that checks if the user's role equals the "ADMIN" role. If the user is an administrator, the function returns the original array of tasks (tasks) without filtering. 

If the user is not an administrator, it uses the filter method to create a new array containing only the tasks where the userId property matches the user `ID`. 

Our `module.exports` now looks like this:

```JavaScript
module.exports = {
 canViewTask,
 scopedTasks,
};
```

Inside `task.js`, let's change our first routes to this:

```JavaScript
router.get("/", authUser, (req, res) => {
 res.json(scopedTasks(req.user, tasks));
});
```

In the code above, we first ensure that only authenticated users can access the subsequent route. Then we pass a list of all the tasks as well as the particular user, scoping the list of projects down to that individual user. Let's test it out:


![user 1 can view all the tasks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j58w2gh5b7dih2ehtz3d.png)


![user 2 can view only tasks linked to it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8fuj7u3nv59sue2a5kb.png)


![user 3 can view onl tasks linked to it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/293cyz9nx3nfbphy6zsd.png)

We can see that the admin, which is user 1, displays all the tasks, `user 2` displays the tasks it is linked to, and likewise, `user 3`. Now, this system is handling the list and permission for our routes.	

And that's a wrap, guys! You can add another route for deleting and creating a task if you want.

## Conclusion

Managing user roles in Node.js is crucial for maintaining a secure and organized web application. By implementing role-based access control, integrating with a database, and using middleware for authentication and authorization, you can establish a robust system that protects sensitive data and ensures a smooth user experience. 

Stay informed about best practices and security updates to keep your application secure and up-to-date.