> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loremstock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementing Role-Based Authorization in Express

> Add RBAC to your Express API using role middleware, MySQL-backed ownership checks, and the db.query() callback pattern. Covers checkRole, 401 vs 403, and resource ownership.

Authorization controls what authenticated users are allowed to do. After `verifyToken` sets `req.user`, authorization middleware checks whether that user's role permits the requested action. This page builds role middleware, ownership checks, and explains 401 vs 403.

## What Is Authorization

Authorization decides what an authenticated user can do. Examples:

* Only `admin` users can delete other users
* Only the post author can edit their post
* Only `moderator` and `admin` can approve content

<Note>
  Authorization always runs AFTER authentication. `verifyToken` sets `req.user` first, then `checkRole` reads `req.user.role`.
</Note>

## Roles in MySQL

The `role` column is defined as an ENUM in the `users` table:

```sql theme={null}
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(191) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  role ENUM('guest', 'user', 'moderator', 'admin') DEFAULT 'user',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

The role is included in the JWT payload at login:

```javascript theme={null}
const token = jwt.sign(
  { id: results[0].id, email: results[0].email, role: results[0].role },
  'secretKey',
  { expiresIn: '1d' }
);
```

From then on, `req.user.role` is available in every middleware after `verifyToken`.

## checkRole Middleware

```javascript theme={null}
// middleware/checkRole.js
function checkRole(requiredRole) {
  return function (req, res, next) {
    if (!req.user) {
      return res.status(401).json({ message: 'Authentication required' });
    }

    if (req.user.role !== requiredRole) {
      return res.status(403).json({
        message: 'Access denied. Required role: ' + requiredRole + '. Your role: ' + req.user.role
      });
    }

    next();
  };
}

module.exports = checkRole;
```

Usage — chain verifyToken then checkRole:

```javascript theme={null}
const verifyToken = require('../middleware/verifyToken');
const checkRole = require('../middleware/checkRole');

// Admin-only: delete a user
app.delete('/api/users/:id', verifyToken, checkRole('admin'), function (req, res) {
  const sql = 'DELETE FROM users WHERE id = ?';
  db.query(sql, [req.params.id], function (err, result) {
    if (err) return res.status(500).json({ message: 'Error deleting user', err });
    if (result.affectedRows === 0) return res.status(404).json({ message: 'User not found' });
    return res.status(204).send();
  });
});
```

## Allowing Multiple Roles

```javascript theme={null}
// middleware/checkRoles.js
function checkRoles() {
  const allowedRoles = Array.from(arguments);
  return function (req, res, next) {
    if (!req.user) {
      return res.status(401).json({ message: 'Authentication required' });
    }
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ message: 'Access denied: insufficient permissions' });
    }
    next();
  };
}

module.exports = checkRoles;

// Allow admin OR moderator
app.put('/api/posts/:id/approve',
  verifyToken,
  checkRoles('admin', 'moderator'),
  function (req, res) {
    const sql = "UPDATE posts SET status = 'approved' WHERE id = ?";
    db.query(sql, [req.params.id], function (err, result) {
      if (err) return res.status(500).json({ message: 'Error approving post', err });
      if (result.affectedRows === 0) return res.status(404).json({ message: 'Post not found' });
      return res.status(200).json({ message: 'Post approved' });
    });
  }
);
```

## Resource Ownership Check

A user should only edit their own posts, not other users' posts:

```javascript theme={null}
// middleware/ownerOnly.js
const db = require('../config/db');

function ownerOnly(req, res, next) {
  const sql = 'SELECT user_id FROM posts WHERE id = ?';

  db.query(sql, [req.params.id], function (err, results) {
    if (err) {
      return res.status(500).json({ message: 'Error checking ownership', err });
    }
    if (results.length === 0) {
      return res.status(404).json({ message: 'Post not found' });
    }

    // Compare MySQL user_id with JWT id
    if (results[0].user_id !== req.user.id) {
      return res.status(403).json({ message: 'Access denied: you do not own this resource' });
    }

    next();
  });
}

module.exports = ownerOnly;
```

Usage:

```javascript theme={null}
app.put('/api/posts/:id', verifyToken, ownerOnly, function (req, res) {
  const sql = 'UPDATE posts SET content = ? WHERE id = ?';
  db.query(sql, [req.body.content, req.params.id], function (err, result) {
    if (err) return res.status(500).json({ message: 'Error updating post', err });
    return res.status(200).json({ message: 'Post updated' });
  });
});
```

## 401 vs 403

<Warning>
  Classic exam question. Know the exact difference.
</Warning>

| Code    | Name         | Meaning           | When to Use                                 |
| ------- | ------------ | ----------------- | ------------------------------------------- |
| **401** | Unauthorized | Not authenticated | No token, expired token, invalid token      |
| **403** | Forbidden    | Not authorized    | Token valid but role/ownership check failed |

Simple rule: 401 = "I don't know who you are." 403 = "I know who you are, but you can't do this."

## Full Admin Route Example

```javascript theme={null}
const verifyToken = require('./middleware/verifyToken');
const checkRole = require('./middleware/checkRole');
const checkRoles = require('./middleware/checkRoles');

// Admin only: get all users
app.get('/api/admin/users', verifyToken, checkRole('admin'), function (req, res) {
  const sql = 'SELECT id, name, email, role, created_at FROM users';
  db.query(sql, function (err, results) {
    if (err) return res.status(500).json({ message: 'Error fetching users', err });
    return res.status(200).json({ data: results });
  });
});

// Admin or moderator: suspend user
app.patch('/api/admin/users/:id/suspend',
  verifyToken,
  checkRoles('admin', 'moderator'),
  function (req, res) {
    const sql = 'UPDATE users SET suspended = 1 WHERE id = ?';
    db.query(sql, [req.params.id], function (err, result) {
      if (err) return res.status(500).json({ message: 'Error suspending user', err });
      if (result.affectedRows === 0) return res.status(404).json({ message: 'User not found' });
      return res.status(200).json({ message: 'User suspended' });
    });
  }
);
```

## RBAC vs ABAC

| Model    | Decision Based On                        | Complexity                             |
| -------- | ---------------------------------------- | -------------------------------------- |
| **RBAC** | User's role                              | Simple — good for most apps            |
| **ABAC** | User + resource + environment attributes | Powerful — used for fine-grained rules |

ABAC example: "User can edit a post only if they are the author AND the post is still in 'draft' status."

For most MySQL-backed APIs, RBAC is sufficient.

## Key Terms

| Term                   | Definition                                                                               |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| **RBAC**               | Role-Based Access Control. Permissions assigned via roles.                               |
| **ABAC**               | Attribute-Based Access Control. Decisions on multiple attributes.                        |
| **checkRole**          | Middleware factory: `checkRole('admin')` returns middleware that checks `req.user.role`. |
| **Resource ownership** | Verifying the requesting user created the resource being modified.                       |
| **401**                | Unauthenticated: token is missing, invalid, or expired.                                  |
| **403**                | Forbidden: token is valid but access is denied.                                          |

## Common Mistakes

<Accordion title="Checking roles before verifyToken">
  checkRole reads req.user.role. If verifyToken has not run yet, req.user is undefined and checkRole crashes. Always: verifyToken THEN checkRole.
</Accordion>

<Accordion title="Returning 403 when token is missing">
  No token = 401. Only return 403 when the identity is confirmed but the action is denied.
</Accordion>

<Accordion title="Not parameterizing the ownership query">
  Always use db.query(sql, \[req.params.id], ...) with ? placeholders. Never concatenate req.params.id directly into the SQL string.
</Accordion>
