How to Implement RBAC in Node.js

Role-Based Access Control (RBAC) is a security method that restricts system access to authorized users based on their specific roles. This article provides a straightforward guide on how to implement RBAC in a Node.js application using Express, demonstrating how to define roles, create authorization middleware, and secure application routes.

1. Define Roles and Permissions

The first step in implementing RBAC is defining the roles and their associated permissions. You can manage this using a simple configuration object.

const roles = {
  admin: {
    permissions: ['read', 'write', 'delete']
  },
  editor: {
    permissions: ['read', 'write']
  },
  viewer: {
    permissions: ['read']
  }
};

2. Create the Authorization Middleware

To enforce these permissions, write a middleware function that intercepts incoming requests. This middleware extracts the user’s role (usually from a decoded JWT or session), checks the defined permissions for that role, and either allows or denies access.

function authorize(requiredPermission) {
  return (req, res, next) => {
    // Assume req.user is populated by prior authentication middleware
    const userRole = req.user && req.user.role;

    if (!userRole || !roles[userRole]) {
      return res.status(403).json({ message: 'Access Denied: No role assigned' });
    }

    const hasPermission = roles[userRole].permissions.includes(requiredPermission);

    if (!hasPermission) {
      return res.status(403).json({ message: 'Access Denied: Insufficient permissions' });
    }

    next();
  };
}

3. Secure Your Routes

Once the middleware is defined, apply it to your Express routes. You can secure individual endpoints by passing the authorize middleware with the specific permission required to access that route.

const express = require('express');
const app = express();

// Mock authentication middleware to simulate a logged-in user
app.use((req, res, next) => {
  req.user = { id: 1, role: 'editor' }; // Change to 'admin' or 'viewer' to test
  next();
});

// Public route
app.get('/public', (req, res) => {
  res.send('This is public data.');
});

// Protected routes using RBAC middleware
app.get('/data', authorize('read'), (req, res) => {
  res.send('Here is the read-only data.');
});

app.post('/data', authorize('write'), (req, res) => {
  res.send('Data successfully written.');
});

app.delete('/data', authorize('delete'), (req, res) => {
  res.send('Data successfully deleted.');
});

app.listen(3000, () => console.log('Server running on port 3000'));

By implementing this pattern, you centralize permission management, making it easy to scale roles and protect endpoints as your Node.js application grows.