SyncTrix logoSyncTrix
All articles
Security12 min read

Authorisation beyond roles: why RBAC stops scaling and what replaces it

Role explosion is a symptom, not the disease. The real problem is that permissions depend on the relationship between the actor and the resource, which roles cannot express.

By Lena Voss
Authorisation beyond roles: why RBAC stops scaling and what replaces it

Every permissions system starts with three roles and a switch statement. Two years later there are forty roles, eleven of them are named after a single customer, and nobody can answer whether a given user can see a given record without running the application. The failure is predictable because roles encode who someone is, while almost every real requirement is about the relationship between that person and a specific object.

01Role explosion is the visible symptom

The first sign of trouble is roles multiplying. `manager` becomes `regional_manager`, then `regional_manager_readonly`, then `regional_manager_emea_with_export`. Each new role exists because a customer needed a combination the existing set could not express, and each one has to be understood by every future engineer who touches an authorisation check.

The second sign is checks drifting apart. The same rule is implemented in the API handler, in a background job, in the export path and in the admin tool, and one of them is subtly wrong. Nobody notices until a customer sees another tenant's data, at which point the question 'where is this rule defined' has four answers. Both symptoms trace to the same cause: authorisation logic living inside application code rather than being a thing the system can reason about.

02The requirement roles cannot express

Take a request that sounds trivial: a user can edit a document if they own it, or if they are an editor on the folder containing it, or if they belong to a team that was granted access to the parent workspace, unless the document is locked, and always if they are an organisation administrator. There is no set of roles that expresses this, because the answer depends on a graph of relationships between the user and this particular object.

This is the shape most business requirements actually take, and it is why teams end up writing bespoke queries that join four tables inside every handler. The permission is not an attribute of the user - it is a path through the data. Once you recognise that, the architecture question becomes how to represent and evaluate those paths consistently rather than how to name more roles.

03The three models and where each one lands

Three models cover essentially all production systems, and they compose rather than compete. Most mature systems use roles for coarse capability, attributes for contextual conditions, and relationships for object-level decisions.

ModelDecides onGood atBreaks when
RBACThe actor's assigned roleCoarse capability; simple admin UIPermissions depend on the specific object
ABACAttributes of actor, resource and contextConditional rules - time, location, clearance, tenantRules multiply and become hard to audit or reverse
ReBACRelationship graph between actor and objectHierarchies, sharing, inheritance, group nestingGraph depth and write amplification at scale
Hybrid (typical)Roles for capability, relationships for objectsMatches how products actually workRequires a real policy layer, not scattered ifs
Authorisation models compared

04Centralise the decision, not the data

The structural fix is to make authorisation a component with an interface rather than logic sprinkled through handlers. A policy decision point takes the actor, the action, the resource and some context, and returns allow or deny. Every call site asks the same component the same way, which means there is exactly one place to change a rule and exactly one place to audit it.

The mistake is centralising the data too. If your policy service has to fetch resource state on every call, you have added a network hop and a hard dependency to every request in the system. The workable pattern is to pass the data with the query, or to replicate the relationship tuples the policy engine needs into its own store and keep them current. Which one you pick determines your latency profile more than the policy language does.

  • One decision interface, called identically from API, jobs, exports and admin tooling.
  • Policy as versioned artefacts in source control, reviewed like code.
  • Deny by default; an unmatched request is not an allow.
  • Log every decision with the inputs, not just the outcome.

05The list problem nobody plans for

Checking a single object is easy. Listing everything a user can see is the query that breaks naive implementations, because you cannot fetch a page of results and then filter it - you get short pages, wrong pagination and O(n) policy calls. This is the point at which teams discover their elegant policy service does not fit their access patterns.

There are two viable answers. Either the authorisation system can return the set of accessible resource identifiers so you can filter in the database, or it can produce a predicate that you push into the query itself. Anything else means loading candidate rows to discard them, which is fine at a thousand rows and untenable at a million. Decide this before you pick a policy engine, because retrofitting it is a rewrite.

06Migrating without a big bang

Do not rewrite authorisation across the application in one change. Introduce the decision interface, implement it initially by calling the existing role checks, and move call sites over one at a time. Behaviour is unchanged, so the risk is low, and you end up with a complete inventory of every authorisation decision the system makes - which most teams have never had.

Then run the new model in shadow mode: evaluate both the old and new logic, serve the old answer, and log disagreements. The disagreements are your specification. In every migration we have run, the shadow log surfaced existing bugs in the old rules within days, and fixing those before cutover is what makes the switch uneventful.

Topics

rbac vs abacrelationship based access controlauthorisation architecturepolicy engine opamulti tenant permissionsfine grained authorization

Lena Voss

Lead Architect · SyncTrix

Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.

Building something like this?

SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.