Designing a Permissions System: RBAC vs ABAC for Your Product

Veld Systems||6 min read

Access control is one of those systems that every product needs and most teams get wrong on the first attempt. Not because the concepts are difficult, but because the initial requirements always look simpler than what the product actually evolves into. You start with "admins can do everything, users can only see their own stuff," and 18 months later you are patching a tangled mess of role checks scattered across your codebase.

We have rebuilt permissions systems for products that outgrew their original design. The cost of getting this wrong is not just engineering time. It is security vulnerabilities, customer escalations, and features you cannot ship because the permissions model will not support them.

Understanding the Two Models

RBAC: Role Based Access Control

RBAC assigns permissions to roles, and roles to users. A user with the "editor" role can edit content. A user with the "viewer" role can only read. The permissions are static, defined at the role level, and applied uniformly to everyone who holds that role.

The appeal of RBAC is its simplicity. Your database has a roles table, a permissions table, and a join table. Your middleware checks whether the user's role includes the required permission. Product managers can reason about who can do what by looking at a role matrix.

RBAC works best when your access control rules are role driven and relatively uniform. Internal tools, content management systems, and products where every user in a given tier has identical capabilities. If you can describe your permissions as "all editors can do X," RBAC is your model.

ABAC: Attribute Based Access Control

ABAC makes access decisions based on attributes of the user, the resource, the action, and the environment. Instead of "editors can edit content," ABAC says "a user can edit a document if they are in the same organization as the document owner, the document is not locked, and the request is coming from an approved IP range."

The power of ABAC is its expressiveness. You can encode almost any access control rule as a policy. The tradeoff is complexity, both in implementation and in reasoning about the system. When a user reports they cannot access something, debugging an ABAC system requires understanding which attributes failed the policy evaluation.

ABAC works best when access decisions depend on relationships and context, not just identity. Multi tenant platforms, healthcare systems, financial products, and any domain where regulatory compliance dictates fine grained access rules.

How to Choose

The decision framework we use with our clients comes down to three questions:

1. Are your permissions mostly about "who" or about "what"?

If the answer is "who," meaning access is determined by the user's role or tier, start with RBAC. If the answer is "what," meaning access depends on the specific resource, its state, or its relationship to the user, you need ABAC or at minimum a hybrid.

A project management tool is a good example of the hybrid case. You need roles (admin, member, viewer) but you also need resource level rules (a user can only see projects they are assigned to, a task can only be edited by its assignee or a project admin). Pure RBAC cannot express "a user can edit tasks they created" without creating a role per user per resource, which defeats the purpose.

2. Is your product multi tenant?

Multi tenancy changes everything about permissions. If you are building a multi tenant SaaS, you need at minimum tenant scoping on every permission check. A "manager" in Organization A should never see data from Organization B, regardless of their role.

Most multi tenant products end up with RBAC for within tenant permissions (what can this user do inside their organization) and ABAC style checks for cross tenant isolation (this user can only access resources belonging to their tenant). If you are building a SaaS product, plan for this from day one. Retrofitting tenant isolation into an existing RBAC system is one of the more painful migrations we encounter.

3. Will your customers need to configure their own permissions?

This is the question that catches the most teams off guard. Enterprise customers will eventually ask for custom roles, and when they do, your permissions system needs to support it.

If you built a fixed set of roles (admin, editor, viewer), you now need to make those roles configurable. That means your roles and permissions need to be data driven, not hardcoded in your application logic. This is not a fundamental architectural change if you planned for it, but it is a significant rewrite if you did not.

The Architecture We Recommend

For most SaaS products we build through our full stack development service, we implement a layered permissions architecture that starts simple and scales:

Layer 1: Tenant isolation. Every database query is scoped to the user's tenant. This is non negotiable and happens at the data access layer, not the application layer. Row Level Security in PostgreSQL is excellent for this.

Layer 2: Role based permissions. Users have roles within their tenant. Roles map to a set of permissions. The permission check is a simple lookup: does this user's role include the required permission for this action?

Layer 3: Resource level policies (when needed). For actions that depend on the relationship between the user and the specific resource, you add attribute checks on top of the role check. "User has the editor role AND is assigned to this project."

This layered approach gives you the simplicity of RBAC for the 80% of checks that are role driven, and the expressiveness of ABAC for the 20% that need context.

Implementation Patterns That Scale

Centralize your permission checks. Whether you use middleware, a policy engine, or a service layer, permission checks should go through a single code path. Scattering role checks across your codebase is how you end up with inconsistent enforcement and security gaps. Our web app security checklist covers this alongside other foundational patterns.

Make permissions cacheable. For RBAC, a user's effective permissions rarely change during a session. Cache the resolved permission set on login or token creation and invalidate it when roles change. For ABAC, cache the static attributes (user role, tenant membership) and evaluate dynamic attributes (resource ownership, time of day) at query time.

Separate authorization from authentication. Your authentication system (login, sessions, tokens) should not contain authorization logic. Authentication tells you who the user is. Authorization tells you what they can do. These are different concerns with different caching strategies, different failure modes, and different change frequencies.

Design your database schema for permission queries. If your most common query is "can this user perform this action on this resource," your schema should make that query fast. This often means denormalizing the permission lookup path rather than joining across five tables on every request.

Build an audit trail from day one. Every permission check that results in a denial should be logged. This is essential for debugging ("why cannot I access this?"), for compliance, and for identifying patterns that suggest your permission model needs refinement.

The Mistakes We See Most Often

Hardcoding roles in application logic. If your codebase has `if (user.role === "admin")` scattered throughout, you have coupled your business logic to your permission model. When you need to add a new role or change what "admin" means, you are editing dozens of files.

Checking permissions in the frontend only. Frontend permission checks are a UX feature, not a security feature. They hide buttons the user should not click. Your API must enforce permissions independently, because anyone can call your API directly. This is foundational to good API design.

Over engineering from the start. We have seen teams build full ABAC policy engines for products with three roles. The complexity slowed down every feature they shipped for a year. Start with the simplest model that covers your current requirements, and build the extension points so you can add complexity when real requirements demand it.

When to Invest in This

The best time to design your permissions architecture is before you build your first protected endpoint. The second best time is now. If you are running a SaaS product with growing enterprise demand and your permissions system is held together with if statements and good intentions, it is time for a proper design.

Talk to us about your permissions architecture. We will assess what you have, identify the gaps, and design a system that supports where your product is headed.

Ready to Build?

Let us talk about your project

We take on 3-4 projects at a time. Get an honest assessment within 24 hours.