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 "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!
Top comments (0)