Stop 08 · Authentication Across the StackTopics · 7/8

Role-Based Access Control

Part of the Full Stack Developer Roadmap

In short

Being logged in isn't the same as being allowed. A student can view marks, a teacher can edit them, and only an admin can delete a course. RBAC writes those rules down once.

Restricting actions or resources based on a user's assigned role (admin, editor, viewer) — enforced on the backend, and reflected in what the frontend even shows as an option.

The blob character holds a keyring with three coloured keys in front of three matching doors.

Think of it like this

The hostel keyring

The warden has keys to every room. Mess staff get the kitchen and store room. Students get only their own room. Nobody hands out keys person by person; keys come with the job. RBAC works the same: permissions belong to roles, and users get roles.

Watch it happen

Roles and what they can do

Roles
admin
teacher
student
course:create
course:delete
marks:edit
marks:view

1/5Three roles. Each user is given one or more of them.

The big ideas

What you need to know

Users get roles

Riya is a teacher. You never attach permissions to Riya directly, so changing a role updates everyone who has it.

Roles get permissions

Name permissions as resource:action, like marks:edit. Check permissions in code, not role names.

Check on the server

Hiding a button isn't security. Every protected route must check the permission on the backend.

Deny by default

If a route forgets to declare a permission, refuse the request instead of allowing it.

See it in code

Walk through the code

ts
1const ROLE_PERMISSIONS: Record<string, string[]> = {2  student: ["marks:view"],3  teacher: ["marks:view", "marks:edit"],4  admin: ["marks:view", "marks:edit", "course:create", "course:delete"],5};6 7export function requirePermission(permission: string) {8  return (req: Request, res: Response, next: NextFunction) => {9    const roles: string[] = req.user?.roles ?? [];10    const allowed = roles.some((r) => ROLE_PERMISSIONS[r]?.includes(permission));11    if (!allowed) return res.status(403).json({ error: { code: "FORBIDDEN" } });12    next();13  };14}15 16app.patch("/marks/:id", requireAuth, requirePermission("marks:edit"), updateMarks);

1/4Each role maps to a list of permissions. In bigger apps this lives in the database.

requireAuth sets req.user from the session or token. Request, Response and NextFunction are imported from express.

Save yourself the pain

Common mistakes

Oops

Writing if (user.role === 'admin') all over the codebase.

Do this instead

Check permissions like marks:edit instead. Adding a new role then means editing one table, not fifty if statements.

Oops

Only hiding the Delete button for non-admins in the frontend.

Do this instead

Anyone can call the API with curl. The backend route must check the permission too.

Your mission

Lock down three routes

~30 min

Quick check

A logged-in student calls DELETE /courses/5. Which status should the API return?

Go deeper

  1. 1Add a role field to your user model and gate one backend route and one frontend UI element by it.
  2. 2Build a reusable backend middleware and a frontend hook that both check the required role.
  3. 3Remember: hiding a button on the frontend is not the same as actually restricting the action.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge