Skip to content
Innovialabs

RBAC vs ABAC: How to Design Roles and Permissions in an App

Practice4 min

In short

Start with RBAC — roles that bundle named permissions — and add ABAC-style rules only where access depends on data, such as "own deals" or "deals of my branch", always checking both on the server.

The short answer

  • RBAC (role-based access control): a user gets roles, a role gets permissions. "Manager can edit deals." Simple to understand and audit.
  • ABAC (attribute-based access control): a decision is made from attributes of the user, the resource and the context. "A manager can edit a deal if it belongs to their branch and is not closed."

Most real apps use a hybrid: RBAC answers "can this person do this type of action at all", attribute rules answer "on which records".

How they compare

RBACABAC
Question it answersWhat actions does a role allow?Is this action allowed on this object now?
Example in CRMSales manager can view dealsManager sees only deals of their branch
Setup effortLowHigher: rules need design and testing
AuditingEasy: list roles and permissionsHarder: you must read the rules
Typical riskRole explosionRules nobody understands

Model permissions, not roles

The most common mistake is checking roles in code: if (user.role === "admin"). When a new role appears, you must hunt for every such check. Instead, check permissions and let roles be just bundles of them.

CREATE TABLE permissions (
  id   serial PRIMARY KEY,
  code text UNIQUE NOT NULL          -- e.g. 'deals.view', 'deals.edit', 'users.manage'
);
CREATE TABLE roles (
  id   serial PRIMARY KEY,
  name text UNIQUE NOT NULL
);
CREATE TABLE role_permissions (
  role_id       int REFERENCES roles(id),
  permission_id int REFERENCES permissions(id),
  PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
  user_id int REFERENCES users(id),
  role_id int REFERENCES roles(id),
  PRIMARY KEY (user_id, role_id)
);

Permission codes live in code as constants; roles live in the database and can be edited in an admin panel without a release.

Add attributes where data matters

Scope rules usually depend on a few attributes: owner, team, branch, record status. Put them in one policy function per resource:

function canEditDeal(user: User, deal: Deal): boolean {
  if (!user.permissions.has("deals.edit")) return false;
  if (deal.status === "closed") return user.permissions.has("deals.edit_closed");
  if (user.permissions.has("deals.edit_any")) return true;
  if (deal.ownerId === user.id) return true;
  return user.permissions.has("deals.edit_branch") && deal.branchId === user.branchId;
}

For lists, apply the same rule as a database filter, not by loading everything and hiding rows in the UI. Otherwise the API still returns other branches' data, and pagination counts are wrong.

Enforce on the server

  • The UI may hide buttons, but every API endpoint checks permissions itself. Requests can be sent directly, bypassing the interface.
  • Check access to the specific object, not just the action. Changing /deals/42 to /deals/43 in a request must not open someone else's deal — this is the classic IDOR / broken object level authorization.
  • Deny by default: a new endpoint without a policy should return 403, not work for everyone.
  • Log permission changes and role assignments as security events.

How to avoid role explosion

Role explosion happens when every exception becomes a new role: "Manager Tashkent", "Manager Samarkand", "Manager Tashkent without export"…

  • Keep roles about job functions; move scope (branch, team, region) into attributes.
  • Allow multiple roles per user instead of creating combined roles.
  • Make rare permissions separate grants rather than new role variants.
  • Review roles periodically: remove unused ones and merge near-duplicates.
  • Show in the admin panel what each role actually allows, in plain language.

Example: admin panel of an online store

  • Roles: Owner, Admin, Content manager, Support, Warehouse.
  • Permissions: products.edit, orders.view, orders.refund, users.manage, settings.payments.
  • Attribute rules: support can view orders but refund only up to a limit set in settings; warehouse sees only orders of its warehouse; nobody can change their own roles.

FAQ

When access does not depend on who owns the data: small internal tools, simple admin panels with a few roles. As soon as you hear "only their own" or "only their branch", you need attribute rules.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.