Implementing Authorization

1. Creating Role-Based Middleware

Example: requireRole factory

export const requireRole = (...roles) => (req, res, next) => {
  if (!req.user)               return next(new HttpError(401));
  if (!roles.includes(req.user.role)) return next(new HttpError(403));
  next();
};

app.delete("/admin/users/:id", requireAuth, requireRole("admin"), removeUser);

2. Checking User Roles

SourceWhere stored
JWT claimreq.user.role after verify
Sessionreq.session.user.role
DatabaseRe-fetch on each request (always-fresh, slower)

3. Implementing Permission Checks

Example: Permission helper

const PERMS = {
  admin:  ["user:*", "post:*"],
  editor: ["post:read", "post:write"],
  user:   ["post:read"]
};

export function hasPermission(role, perm) {
  return (PERMS[role] || []).some(p => p === perm || p.endsWith(":*") && perm.startsWith(p.slice(0, -1)));
}

export const can = (perm) => (req, res, next) =>
  hasPermission(req.user?.role, perm) ? next() : next(new HttpError(403));

4. Using Access Control Lists

Example: ACL with accesscontrol

import AccessControl from "accesscontrol";

const ac = new AccessControl();
ac.grant("user").readOwn("post").createOwn("post");
ac.grant("admin").extend("user").readAny("post").deleteAny("post");

const permission = ac.can(req.user.role).readAny("post");
if (!permission.granted) throw new HttpError(403);

5. Protecting Resource Access

Example: Owner-or-admin

app.delete("/posts/:id", requireAuth, async (req, res, next) => {
  const post = await db.posts.findById(req.params.id);
  if (!post) return next(new HttpError(404));
  if (post.authorId !== req.user.sub && req.user.role !== "admin") {
    return next(new HttpError(403));
  }
  await db.posts.remove(req.params.id);
  res.sendStatus(204);
});

6. Implementing Attribute-Based Control (ABAC)

Example: Policy function

// policies/post.js
export const canEditPost = (user, post) =>
  user.role === "admin" ||
  (post.authorId === user.sub && !post.locked && post.status !== "published");

// route
if (!canEditPost(req.user, post)) throw new HttpError(403);
ModelDecision based on
RBACRole only
ABACUser + resource + environment attributes
ReBACRelationships (Zanzibar / OpenFGA / Cerbos)

7. Creating Authorization Middleware Chain

Example: Compose

app.patch("/orgs/:orgId/projects/:id",
  requireAuth,
  requireOrgMember(),     // sets req.membership
  requireRole("owner","editor"),
  loadProject(),          // sets req.project
  authorize((req) => canEditProject(req.user, req.project)),
  updateProject
);

8. Handling Unauthorized Access

StatusMeaning
401 UnauthorizedMissing or invalid credentials — challenge with WWW-Authenticate
403 ForbiddenAuthenticated but not allowed — do not retry

Example: 401 vs 403

if (!req.user) {
  res.set("WWW-Authenticate", 'Bearer realm="api"');
  throw new HttpError(401);
}
if (!hasPermission(req.user.role, "post:delete")) throw new HttpError(403);

9. Using Scopes for API Access

Example: OAuth scopes

export const requireScope = (...scopes) => (req, res, next) => {
  const token = req.user?.scope?.split(" ") || [];
  const ok = scopes.every(s => token.includes(s));
  ok ? next() : next(new HttpError(403, "Insufficient scope"));
};

app.get("/me/email", requireAuth, requireScope("read:email"), getEmail);

10. Implementing Resource-Level Permissions

Example: Per-row check

// Filter at query time — never trust client to filter
const posts = await prisma.post.findMany({
  where: {
    OR: [
      { authorId: req.user.sub },
      { collaborators: { some: { userId: req.user.sub } } },
      ...(req.user.role === "admin" ? [{}] : [])
    ]
  }
});
Warning: Always enforce row-level access in the database query, not in JS after fetching. Otherwise IDOR vulnerabilities arise.