DEV Community

rabindratamang
rabindratamang

Posted on

How to Implement Role-Based Access Control (RBAC) in Node.js Applications

Role-Based Access Control (RBAC) is a widely used method for managing permissions in modern applications. It ensures that users have appropriate access based on their roles, improving security and maintainability. In this article, we'll explore how to implement RBAC in a Node.js application with Express.js and MongoDB.

Why Use RBAC?

RBAC provides a structured way to control user permissions by assigning them predefined roles. This approach prevents unauthorized access and helps in maintaining a scalable and manageable permission system.

Benefits of RBAC:

  • Security: Restricts unauthorized access to resources.
  • Scalability: Easily accommodates new roles and permissions.
  • Maintainability: Centralized permission control simplifies code maintenance.

Setting Up the Node.js Application

First, create a new Node.js project and install the required dependencies:

mkdir rbac-node-app && cd rbac-node-app
npm init -y
npm install express mongoose jsonwebtoken bcryptjs dotenv
Enter fullscreen mode Exit fullscreen mode
  • Express: For handling HTTP requests.
  • Mongoose: For MongoDB interactions.
  • jsonwebtoken: For user authentication.
  • bcryptjs: For password hashing.
  • dotenv: For managing environment variables.

Define User Roles and Permissions

Let's create a basic role structure. Define roles and permissions in a separate file (roles.js):

const roles = {
  admin: ['create', 'read', 'update', 'delete'],
  editor: ['create', 'read', 'update'],
  user: ['read']
};

module.exports = roles;
Enter fullscreen mode Exit fullscreen mode

Create a User Model

Define a User model using Mongoose (models/User.js):

const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['admin', 'editor', 'user'], default: 'user' }
});

module.exports = mongoose.model('User', UserSchema);
Enter fullscreen mode Exit fullscreen mode

Implement Authentication

Set up user authentication using JWT (auth.js):

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('./models/User');

const register = async (req, res) => {
  const { username, password, role } = req.body;
  const hashedPassword = await bcrypt.hash(password, 10);
  const user = new User({ username, password: hashedPassword, role });
  await user.save();
  res.json({ message: 'User registered' });
};

const login = async (req, res) => {
  const { username, password } = req.body;
  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  const token = jwt.sign({ id: user._id, role: user.role }, 'your_secret_key', { expiresIn: '1h' });
  res.json({ token });
};

module.exports = { register, login };
Enter fullscreen mode Exit fullscreen mode

Implement Middleware for Role-Based Access Control

Create an RBAC middleware (middlewares/rbac.js):

const roles = require('../roles');

const authorize = (requiredPermissions) => {
  return (req, res, next) => {
    const userRole = req.user.role;
    if (!roles[userRole] || !requiredPermissions.every(perm => roles[userRole].includes(perm))) {
      return res.status(403).json({ message: 'Forbidden' });
    }
    next();
  };
};

module.exports = authorize;
Enter fullscreen mode Exit fullscreen mode

Protect Routes with RBAC

Modify your Express routes (routes.js):

const express = require('express');
const { register, login } = require('./auth');
const authorize = require('./middlewares/rbac');
const router = express.Router();
const jwt = require('jsonwebtoken');

// Middleware to verify token
const authenticate = (req, res, next) => {
  const token = req.header('Authorization');
  if (!token) return res.status(401).json({ message: 'Access Denied' });
  try {
    const verified = jwt.verify(token, 'your_secret_key');
    req.user = verified;
    next();
  } catch (err) {
    res.status(400).json({ message: 'Invalid Token' });
  }
};

router.post('/register', register);
router.post('/login', login);
router.get('/admin', authenticate, authorize(['create', 'read', 'update', 'delete']), (req, res) => {
  res.json({ message: 'Admin content' });
});
router.get('/editor', authenticate, authorize(['create', 'read', 'update']), (req, res) => {
  res.json({ message: 'Editor content' });
});
router.get('/user', authenticate, authorize(['read']), (req, res) => {
  res.json({ message: 'User content' });
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Testing the RBAC System

  1. Start the server:
   node server.js
Enter fullscreen mode Exit fullscreen mode
  1. Register users with different roles:
   curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"adminpass","role":"admin"}' http://localhost:3000/register
Enter fullscreen mode Exit fullscreen mode
  1. Login and get a token:
   curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"adminpass"}' http://localhost:3000/login
Enter fullscreen mode Exit fullscreen mode
  1. Access protected routes:
   curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/admin
Enter fullscreen mode Exit fullscreen mode

Conclusion

Implementing Role-Based Access Control (RBAC) in a Node.js application enhances security and scalability. By structuring roles and permissions effectively, you can maintain a robust access control system that ensures users can only access what they are authorized for. This approach is widely used in enterprise applications, making it a must-have skill for developers building secure APIs.

If you found this article helpful, consider sharing it with others!

Top comments (0)