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 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; 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); 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 }; 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; 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; Testing the RBAC System Start the server: node server.js Register users with different roles: curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"adminpass","role":"admin"}' http://localhost:3000/register Login and get a token: curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"adminpass"}' http://localhost:3000/login Access protected routes: curl -H "Authorizatio

Feb 27, 2025 - 08:28
 0
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
  • 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;

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);

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 };

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;

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;

Testing the RBAC System

  1. Start the server:
   node server.js
  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
  1. Login and get a token:
   curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"adminpass"}' http://localhost:3000/login
  1. Access protected routes:
   curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/admin

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!