3 BACKEND API

Authorization (RBAC)

🚦 Authorization: Role-Based Access Control (RBAC)

Authentication (AuthN) asks: "Are you genuinely Mwero?" (Solved with Passwords and JWTs). Authorization (AuthZ) asks: "Now that I know you are Mwero, are you allowed to delete this production database?"

In complex applications, every user needs different permissions. Administrators can delete posts; Users can only read posts; Moderators can edit posts. Controlling these overlapping permissions is handled by RBAC (Role-Based Access Control).


1️⃣ Building the RBAC Middleware

Because we embedded the user's role inside the JWT payload in the previous lesson, every single request that passes through the Auth Middleware now comes with req.user.role.

We can build a second, highly specific Middleware shield that stacks behind the Auth shield.

// A Higher Order Function that takes a list of allowed roles, 
// and returns a customized Express Middleware!
const requireRole = (allowedRolesArray) => {
    
    return (req, res, next) => {
        // 1. Grab the user's role from the JWT (attached by the previous auth middleware)
        const userRole = req.user.role;

        // 2. Check if their role exists in the allowed list
        if (allowedRolesArray.includes(userRole)) {
            next(); // Access granted
        } else {
            res.status(403).json({ error: "Forbidden: You lack the clearance." });
        }
    };
};

2️⃣ Stacking the Shields in Express

Now, we can chain these Middlewares to create impenetrable, highly granular APIs. Express executes Middlewares in the exact array order you define them.

import express from 'express';

const app = express();

// 🟢 PUBLIC ROUTE: No shields. Anyone can view blogs.
app.get('/api/blogs', (req, res) => {
    res.json(blogs);
});

// 🟡 PROTECTED ROUTE: Requires a valid JWT, but ANY role is fine (User, Mod, Admin).
app.post('/api/blogs/comment', requireAuth, (req, res) => {
    res.send("Comment posted by " + req.user.userId);
});

// 🟠 MODERATOR ROUTE: Stacking shields. Must be logged in AND must be a Mod/Admin.
// Array execution: [Verify Token] -> [Verify Role] -> [Execute Route]
app.delete('/api/blogs/comments/:id', requireAuth, requireRole(['Moderator', 'Admin']), (req, res) => {
    res.send("Troll comment deleted.");
});

// 🔴 GOD-LEVEL ROUTE: Only Admins can enter here.
app.delete('/api/database', requireAuth, requireRole(['Admin']), (req, res) => {
    res.send("Database wiped.");
});

3️⃣ Resource-Level Authorization (Ownership)

RBAC (requireRole) determines what actions a user can perform globally. However, it fails at Resource-Level Authorization.

Imagine Bob is a User. Users are allowed to edit blog posts. Bob sends a PUT request to /api/posts/99. Bob passes the requireAuth shield. Bob passes the requireRole(['User']) shield. But wait... Alice wrote Post #99! Bob is trying to edit a post he doesn't own!

To fix this, we must query the Database to check Ownership before allowing the action.

app.put('/api/posts/:postId', requireAuth, async (req, res) => {
    const postId = req.params.postId;
    // The JWT tells us the attacker is Bob (User #5)
    const requestingUserId = req.user.userId; 

    // 1. Fetch the specific post from the DB
    const post = await db.posts.findById(postId);
    if (!post) return res.status(404).send("Not Found");

    // 2. THE OWNERSHIP CHECK
    // If Bob (5) !== Alice (2), and Bob isn't an Admin, block him!
    if (post.authorId !== requestingUserId && req.user.role !== 'Admin') {
        return res.status(403).json({ error: "You do not own this resource." });
    }

    // 3. Safe to update
    await db.posts.update(postId, req.body);
    res.send("Post updated.");
});

4️⃣ Attribute-Based Access Control (ABAC) - The Enterprise Level

As the application scales into the Enterprise level, RBAC stops working. What if a hospital application states: A Doctor can view a Patient's chart ONLY IF they are actively assigned to that patient, AND the current time is between 8 AM and 5 PM, AND the Doctor's security clearance matches the chart's classification level.

Writing this inside 50 Express routes is impossible to maintain.

Enterprises use ABAC (Attribute-Based Access Control) using Policy Engines like CASL or Open Policy Agent (OPA). They centralize all math into mathematical "Policy Blueprints".

Example Architecture using CASL:

Instead of if (post.authorId !== userId), you write pure English logic:

// At the top of your app, you mathematically define what people can do:
if (user.role === 'Admin') {
    can('manage', 'all'); // Admin can do anything
} else {
    can('read', 'Post'); // Standard users can read all posts
    can('update', 'Post', { authorId: user.id }); // Standard users can ONLY update their own posts
}

// Inside the Express Route, it's just one line:
app.put('/api/posts/:id', (req, res) => {
    const post = FetchPost(req.params.id);

    // The Policy Engine calculates all the heavy math automatically!
    if (req.permissions.cannot('update', post)) {
        return res.status(403).send();
    }
});

💡 Summary Flow

Check TypeQuestionThe Express Implementation
AuthenticationAre you who you say you are?Password check -> Issue JWT -> requireAuth Shield.
RBAC (Role)Are you allowed to hit this URL endpoint?requireRole(['Admin']) Shield.
OwnershipAre you allowed to touch this specific row in the database?Db Fetch -> if (dbDoc.owner !== jwt.userId) return 403.

Knowledge Check

Complete this quick quiz to verify your understanding and unlock the next module.