Building Role Based Access Control in Node.js Apps with JWT Authentication

Building Role-Based Access Control in Node.js Apps with JWT Authentication

In modern applications, security is paramount. Role-Based Access Control (RBAC) is a powerful way to manage access to resources by assigning roles to users. Coupled with JSON Web Token (JWT) authentication, RBAC becomes a seamless and secure method for protecting routes in your Node.js application.

1. What is Role-Based Access Control?

Role-Based Access Control (RBAC) restricts access based on users’ roles. For example:

  • Admin: Can manage all resources.
  • Editor: Can modify content but not delete it.
  • Viewer: Can only view content.

RBAC ensures users can only perform actions permitted for their role, reducing security vulnerabilities.

2. Why Use JWT for Authentication?

JWT (JSON Web Token) is a compact, URL-safe token for securely transmitting information between parties. JWT is widely used for its simplicity and stateless nature. It encodes user data and serves as a mechanism for authorization and authentication.

3. Setting Up the Node.js Application

Start by setting up a basic Node.js application with express for handling routes and jsonwebtoken for JWT.

Step 1: Initialize the Project

				
					mkdir rbac-nodejs
cd rbac-nodejs
npm init -y
npm install express jsonwebtoken bcryptjs body-parser dotenv

				
			

Step 2: Create Basic Structure

Your folder structure should look like this:

				
					rbac-nodejs/
│
├── .env
├── server.js
├── routes/
│   ├── auth.js
│   └── user.js
└── middleware/
    ├── authenticate.js
    └── authorize.js

				
			

Step 3: Configure server.js

Create a simple server setup:

				
					require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");

const app = express();
app.use(bodyParser.json());

// Routes
app.use("/auth", require("./routes/auth"));
app.use("/user", require("./routes/user"));

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

				
			

4. Implementing JWT Authentication

JWT consists of three parts: Header, Payload, and Signature. Let’s implement login and token generation.

Create the auth.js Route

				
					const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");

const router = express.Router();
const users = [
    {
        id: 1,
        username: "admin",
        password: bcrypt.hashSync("admin123", 10),
        role: "admin",
    },
    {
        id: 2,
        username: "editor",
        password: bcrypt.hashSync("editor123", 10),
        role: "editor",
    },
];

// Login Endpoint
router.post("/login", (req, res) => {
    const { username, password } = req.body;
    const user = users.find((u) => u.username === username);

    if (!user || !bcrypt.compareSync(password, user.password)) {
        return res.status(401).json({ message: "Invalid credentials" });
    }

    const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: "1h" });
    res.json({ token });
});

module.exports = router;

				
			

5. Adding RBAC to Your Application

Middleware for Authentication

Create authenticate.js to verify the JWT.

				
					const jwt = require("jsonwebtoken");

function authenticate(req, res, next) {
    const token = req.headers["authorization"];
    if (!token) return res.status(403).json({ message: "No token provided" });

    jwt.verify(token.split(" ")[1], process.env.JWT_SECRET, (err, decoded) => {
        if (err) return res.status(401).json({ message: "Unauthorized" });
        req.user = decoded;
        next();
    });
}

module.exports = authenticate;

				
			

Middleware for Authorization

Create authorize.js to restrict access based on roles.

				
					function authorize(roles) {
    return (req, res, next) => {
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ message: "Access forbidden" });
        }
        next();
    };
}

module.exports = authorize;

				
			

6. Protecting Routes

Create the user.js Route

Add endpoints that use RBAC for access control.

				
					const express = require("express");
const authenticate = require("../middleware/authenticate");
const authorize = require("../middleware/authorize");

const router = express.Router();

// Open to all authenticated users
router.get("/profile", authenticate, (req, res) => {
    res.json({ message: `Welcome, user ${req.user.id}!`, role: req.user.role });
});

// Admin-only route
router.delete("/delete", authenticate, authorize(["admin"]), (req, res) => {
    res.json({ message: "User deleted successfully!" });
});

// Editor and Admin route
router.post("/edit", authenticate, authorize(["editor", "admin"]), (req, res) => {
    res.json({ message: "Content edited successfully!" });
});

module.exports = router;

				
			

7. Testing and Securing the App

  1. Generate a Token: Use the /auth/login endpoint to obtain a JWT by providing valid credentials.
  2. Test Routes: Use a tool like Postman to access the endpoints with and without the token.
  3. Secure Your App:
    • Use HTTPS in production.
    • Store JWT secrets securely using dotenv or a similar tool.
    • Implement token blacklisting if necessary.

8. Conclusion

RBAC and JWT together provide a scalable and secure way to manage access in Node.js applications. With this setup, you can dynamically manage user roles and permissions, ensuring secure access to your application resources.

About The Author

Leave a Reply