DEV Community

Oyedele Temitope
Oyedele Temitope

Posted on • Edited on

Beginners Guide to Managing User Roles and Pemissions in Node.js

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.

RBAC ensures 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

How RBAC works is quite straightforward. It associates roles with specific permissions and then assigns these roles to users.

For example, let's say you have roles such as administrator, manager, or employee in your application, each with different permissions. When a user is assigned any of these roles, they get the permissions that come with it.

How to Implement 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 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:

npm i 
Enter fullscreen mode Exit fullscreen mode

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

npm install nodemon
Enter fullscreen mode Exit fullscreen mode

Afterwards, start up the development server by running this command below:

npm run dev
Enter fullscreen mode Exit fullscreen mode

To make an API request, I'll be using Rest Client in VsCode. 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, you have a system with different routes, but none of these routes are protected, i.e., anyone could access the homepage, and the admin page is also accessible. If you send in a request, you can see that even though you are not logged in, you still have access to the admin page.

You need to make sure that you 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 you won't have to go back again to import them every time you want to work on server.js during the tutorial process:

const { ROLE, users } = require("./data");
const { authUser, authRole } = require("./basicAuth");
Enter fullscreen mode Exit fullscreen mode

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. You need 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 the basic authentication.

Create a function called authUser() like so:

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

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. If they do exist, then continue; otherwise, it will be an error.

Head over to server.js and add the authUser to our dashboard routes. Like so:

app.get("/dashboard", authUser, (req, res) => {
 res.send("Dashboard Page");
});
Enter fullscreen mode Exit fullscreen mode

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

dashboard authentication

If there's no user, it'll tell you to sign in:

user has to sign in to access the dashboard

So, you 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 your admin page because this page should only be accessed by an adin. but right now, every user and non-logged-in user can access it.

The first thing you need to do is check if there's a user and also if that user is an admin so they can access the admin page. Inside your server.js, add the authUser to our admin route. Now it will look like this:

app.get("/admin", authUser,(req, res) => {
 res.send("Admin Page");
});
module.exports = { authUser };
Enter fullscreen mode Exit fullscreen mode

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

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

In the code above, 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:

module.exports = { authUser, authRole };
Enter fullscreen mode Exit fullscreen mode

Remember, we already imported everything we'll need in our server.js before the start of this tutorial. So, in your server.js, add the function to the admin route like so:

app.get("/admin", authUser, authRole(ROLE.ADMIN), (req, res) => {
 res.send("Admin Page");
});
Enter fullscreen mode Exit fullscreen mode

First, the route is set up to authenticate the user to ensure they are logged in. Then, it checks if the authenticated user has the admin role. If both conditions are met, the route handler runs and allows access to the "Admin Page." Otherwise, the user is denied access.

For user 1:

admin having only the access to the database

For user 2 or 3:

user 2 not authenticated

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

Recall that in the data, each user has a specific task that they are linked to, except the admin, who can access every user's task. So you'll need to set up that proper authentication so as not to let any user have access to tasks that are 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:

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

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. To do this, you need to first authenticate that the user exists as you only want people to access this page if they are signed in. Let's import the userAuth function into our task.js:

const { authUser } = require("../basicAuth");
Enter fullscreen mode Exit fullscreen mode

Then we add it to the task route:

router.get("/:taskId", setTask, authUser, authGetTask, (req, res) => {
 res.json(req.task);
});
Enter fullscreen mode Exit fullscreen mode

Notice the authGetTask? Don't worry, we'll create that shortly. You need to bring in authentication for different permission for the canviewTask:

const {canViewTask,} = require("../permissions/taskPermission");
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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

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

user 1 can view the task as it is the admin

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:

function scopedTasks(user, tasks) {
 if (user.role === ROLE.ADMIN) return tasks;
 return tasks.filter((task) => task.userId === user.id);
}
Enter fullscreen mode Exit fullscreen mode

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:

module.exports = {
 canViewTask,
 scopedTasks,
};
Enter fullscreen mode Exit fullscreen mode

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

router.get("/", authUser, (req, res) => {
 res.json(scopedTasks(req.user, tasks));
});
Enter fullscreen mode Exit fullscreen mode

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

user 2 can view only tasks linked to it

user 3 can view onl tasks linked to it

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.

Top comments (1)

Collapse
 
deniskiplangat profile image
denis kiplangat

Nice content. Might need some improvement but still nice.