Skip to main content
Authentication Systems Tutorial
CHAPTER 09 Intermediate

Role-Based Access Control (RBAC)

Updated: May 14, 2026
35 min read

# CHAPTER 9

Role-Based Access Control (RBAC)

1. Introduction

Once a user is authenticated, the system must decide what actions they are permitted to perform. Allowing any logged-in user to access the "Delete Database" button is a recipe for disaster. To manage permissions systematically across hundreds or thousands of users, the industry standard is Role-Based Access Control (RBAC). In this chapter, we will design database schemas to support RBAC, understand the hierarchy of roles and permissions, and build authorization middleware to protect sensitive endpoints.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Define the principles of Role-Based Access Control (RBAC).
  • Distinguish between Users, Roles, and Permissions.
  • Design database architectures to support RBAC (Many-to-Many relationships).
  • Implement an Authorization middleware function in Node.js/Express.
  • Enforce granular access control on API routes.

3. Beginner-Friendly Explanation

Imagine a Hospital.
  • The User: Dr. Smith, Nurse Jones, and Janitor Bob.
  • The Permissions: "Prescribe Medicine," "View Patient Records," "Clean Operating Room."
Assigning individual permissions to hundreds of employees is exhausting. Instead, we create Roles.
  • The Role: We create a "Doctor" Role and attach the "Prescribe Medicine" and "View Patient Records" permissions to it.
When Dr. Smith is hired, HR doesn't assign him 50 individual permissions. They simply assign him the Role of "Doctor." Instantly, he inherits all the permissions associated with that role. If Janitor Bob tries to prescribe medicine, the system checks his Role ("Janitor"), sees it lacks that permission, and denies the action.

4. The RBAC Database Architecture

Implementing RBAC usually requires a relational database (like MySQL) with a Many-to-Many architecture.

You need three primary tables:

  1. 1. Users Table: Contains the user's email and password.
  1. 2. Roles Table: Contains the roles (e.g., Admin, Editor, Viewer).
  1. 3. UserRoles Table (Pivot/Junction Table): Links a specific User ID to a specific Role ID.

*Advanced Implementations:* Large enterprises add a fourth and fifth table: Permissions Table (e.g., deletepost) and a Role_Permissions Pivot Table to link specific actions to specific roles.

5. Simple Implementation: The Role Column

For smaller projects, you can skip complex Pivot tables and simply add a role column directly to the users table. We will use this approach for our coding example.
sql
123456
-- A simplified Users table for small applications
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(255),
    role VARCHAR(50) DEFAULT 'user' -- Possible values: 'user', 'editor', 'admin'
);

6. Passing the Role in the JWT

When a user logs in, we query the database, find their role, and embed it directly into the JWT payload.
javascript
123456
// During the login process...
const payload = {
    userId: user.id,
    role: user.role // e.g., "admin"
};
const token = jwt.sign(payload, SECRET_KEY);

7. Building the Authorization Middleware (AuthZ)

We already built authenticateToken to verify the JWT. Now we build a factory function that creates specific Role-checkers.
javascript
12345678910111213141516171819
// THE AUTHORIZATION MIDDLEWARE
// This function accepts an array of allowed roles
function authorizeRoles(...allowedRoles) {
    // It returns a standard Express middleware function
    return (req, res, next) => {
        // req.user was populated by the authenticateToken middleware!
        const userRole = req.user.role;

        // Check if the user's role is in the list of allowed roles
        if (!allowedRoles.includes(userRole)) {
            return res.status(403).json({ 
                error: "Forbidden. You do not have permission to access this resource." 
            });
        }
        
        // Success! They have the correct role.
        next();
    };
}

8. Protecting Routes with RBAC

Now we can chain our middlewares together sequentially. The request must pass AuthN (is the token valid?) AND AuthZ (is the role allowed?) before reaching the logic.
javascript
12345678910111213141516171819202122232425
// Only Admins can delete users
app.delete('/api/users/:id', 
    authenticateToken, 
    authorizeRoles('admin'), 
    (req, res) => {
        // Execute deletion logic...
        res.json({ message: "User deleted." });
});

// Both Admins AND Editors can publish posts
app.post('/api/posts', 
    authenticateToken, 
    authorizeRoles('admin', 'editor'), 
    (req, res) => {
        // Execute publish logic...
        res.json({ message: "Post published." });
});

// Standard users (and above) can view their profile
app.get('/api/profile', 
    authenticateToken, 
    authorizeRoles('admin', 'editor', 'user'), 
    (req, res) => {
        res.json({ message: "Viewing profile." });
});

9. Backend Workflow: Dynamic Permissions (ABAC)

RBAC is excellent, but sometimes roles aren't enough. What if an "Editor" is only allowed to edit posts *they wrote*, but not posts written by other Editors? This requires Attribute-Based Access Control (ABAC). Inside the route handler, you must query the database for the specific post, check the authorid of the post, and compare it to the req.user.userId. If they don't match, return a 403 Forbidden error.

10. Best Practices

  • Fail Closed, Not Open: When designing authorization logic, your default assumption must be "Deny Access." You should only grant access if a specific, positive condition is met. If an error occurs during the authorization check, the system must fail "Closed" (deny access), never "Open" (grant access).

11. Common Mistakes

  • Trusting Client-Side Roles: Never rely on a React frontend sending { role: 'admin' } in a JSON payload. The client can easily manipulate this data using Developer Tools. Always extract the user's role from the mathematically verified JWT payload (req.user.role) or by querying the database directly on the server.

12. Exercises

  1. 1. Explain the architectural benefit of mapping Permissions to Roles, rather than mapping Permissions directly to individual Users.

13. Coding Challenges

  • Challenge: Look at the authorizeRoles factory function. Rewrite this conceptual logic in pure PHP. Write a function checkrole($requiredrole) that checks a hypothetical $SESSION['role'] variable. If it does not match, use header("HTTP/1.1 403 Forbidden"); and exit().

14. MCQs with Answers

Question 1

In a standard RBAC (Role-Based Access Control) architecture, what is the hierarchical relationship between Users, Roles, and Permissions?

Question 2

When implementing Authorization middleware in Express.js, what HTTP status code is the industry standard for rejecting a request where the user is successfully authenticated, but their Role lacks the required permissions?

15. Interview Questions

  • Q: Differentiate between Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). Provide a specific scenario where RBAC is insufficient and ABAC must be utilized.
  • Q: Explain the design pattern of creating Authorization Middleware factory functions (like authorizeRoles('admin', 'editor')) in Express.js. Why is this preferable to hardcoding if (req.user.role !== 'admin') inside every single route handler?

16. FAQs

Q: Can a user have more than one role? A: Yes! In larger systems, the database uses a Pivot Table (User_Roles) allowing one user to hold the "Moderator" role AND the "Billing Admin" role simultaneously. Your authorization middleware would need to check if *any* of the user's roles match the required role for the endpoint.

17. Summary

In Chapter 9, we moved beyond proving identity to enforcing access. We explored Role-Based Access Control (RBAC), a scalable system that groups granular permissions into manageable Roles. We demonstrated how to embed the user's role securely within the JWT payload. Finally, we constructed dynamic Express.js authorization middleware, allowing us to elegantly chain AuthN and AuthZ checks to protect specific API endpoints based on the user's administrative level.

18. Next Chapter Recommendation

We have built our own secure authentication and authorization systems from scratch. But what if we want to let users log in using their Google or GitHub accounts? Proceed to Chapter 10: OAuth and Social Login Systems.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·