Authorization40-55 min
Role-Based Access Control Implementation
This guide implements RBAC with centralized permissions, backend guards, frontend guards, database checks, and a simple verification checklist.
Node.jsReactTypeScript
Prerequisites
- An authenticated user object available on API requests.
- A role field on users or memberships.
- A list of actions each role should be allowed to perform.
- Protected backend routes that need authorization.
1
Model roles and permissions
Start simple. Most products need roles like owner, admin, member, and viewer before they need a complex permission editor.
Implementation snippet
export const permissions = {
owner: ["team:read", "team:update", "billing:manage", "users:invite", "users:remove"],
admin: ["team:read", "team:update", "users:invite"],
member: ["team:read"],
viewer: ["team:read"],
} as const;
export type Role = keyof typeof permissions;
export type Permission = (typeof permissions)[Role][number];2
Create a permission helper
Implementation snippet
export function can(role: Role, permission: Permission) {
return permissions[role]?.includes(permission) ?? false;
}3
Protect backend routes
- Run authentication middleware first.
- Load the user's role for the relevant workspace, organization, or account.
- Check the required permission before the route handler.
- Return 403 when the user is authenticated but not allowed.
Implementation snippet
export function requirePermission(permission: Permission) {
return async (req, res, next) => {
const membership = await memberships.findByUserAndTeam(req.user.id, req.params.teamId);
if (!membership || !can(membership.role, permission)) {
return res.status(403).json({ message: "You do not have access to this action" });
}
req.membership = membership;
next();
};
}
app.post("/teams/:teamId/invites", requireAuth, requirePermission("users:invite"), createInvite);4
Hide unavailable UI actions
Frontend checks improve usability, but they do not replace backend authorization.
Implementation snippet
function InviteButton({ role }: { role: Role }) {
if (!can(role, "users:invite")) return null;
return <button>Invite teammate</button>;
}5
Store roles safely
- Store role at the membership level for team-based products.
- Allow one owner or multiple owners depending on your recovery policy.
- Prevent users from changing their own role to a higher privilege.
- Audit role changes with who changed what and when.
6
Verification checklist
Checklist
- A viewer cannot call admin-only API routes directly.
- A member cannot invite users unless the permission list allows it.
- An admin cannot access billing if only owner has `billing:manage`.
- UI hides unavailable actions but backend still rejects forged requests.
- Role changes take effect after token/session refresh or next API role lookup.