Software architecture
When roles aren't enough: designing authorization that stays manageable
Giving someone a role is easy to understand. A support agent handles tickets. A supervisor reviews exceptions. An administrator manages the application.
The awkward questions start a little later. Can an agent edit a ticket assigned to someone else? Does a supervisor have access across every region? Should an administrator be allowed to change a closed record without an explanation?
Those questions do not necessarily mean the role model is wrong. They mean a role is only part of an access decision. A useful design gives those additional rules a place to live, instead of making each screen work them out again.
Start with the rule, not the interface
Jason Marchalonis's native OutSystems authorization article describes organizing entity access through policies and rules. Its pseudocode uses an administrator check and record ownership to decide whether a user may read, update, or delete a record.
The matrix below makes that particular code example easier to inspect. It is derived from the published pseudocode, not from a production access review or measured customer results.

The interesting cell is the owner's delete permission. Owning something does not automatically mean being allowed to perform every action on it. That distinction should be a deliberate business decision, not an accidental consequence of how a button was wired.
Separate the screen from the operation
An agent may need access to a ticket screen without having permission to change every ticket it can display. A menu item tells a person where to go. It does not settle which records a server may return or update.
Jason's RoleShield article looks at managing resource access, including screens and menus, on top of existing OutSystems capabilities. That is a different concern from deciding whether one particular record can be changed.
Both concerns matter. Keeping them distinct avoids treating a successful screen-access check as a blanket approval for everything behind that screen. The older articles provide implementation context; they are not a security certification for a current application.
Describe one real business decision at a time
Consider an illustrative maintenance-ticket application. An agent works in a region, a ticket has an assigned owner, and completed work is retained for reference. These are proposed rules, not a description of a customer system:
| Action | Proposed rule | Why the role alone is insufficient |
|---|---|---|
| View a ticket | Agent belongs to the ticket's region | The same role exists in other regions |
| Update work notes | Agent owns an open ticket | Ownership and status can change |
| Reassign a ticket | Supervisor covers its region | Supervisors are not necessarily global |
| Correct a closed ticket | Separate correction permission and recorded reason | Ordinary editing should not rewrite history |
Notice that this example does not copy the earlier administrator bypass. Whether to allow a broad exception deserves a separate decision. Reusing a policy structure does not require reusing every policy rule.
Plain-language examples are useful here. Give a teammate a user, a record, and an action. Can they explain the expected decision without opening three screens and searching a collection of event handlers? If not, the application probably needs clearer rules before it needs another role.
Put enforcement where it cannot be skipped
A hidden edit button can prevent confusion, but it is not the access control itself. The server must still check the request. OWASP recommends default denial and checking permissions on each request in a trusted location. Authorization guidance.
For the maintenance example, the service handling the update should establish the caller from trusted authentication context and evaluate the requested record and action. It should not trust an owner identifier or a claimed supervisor role supplied by the browser.
The policy also needs to cover the paths people do not click manually. Imports, APIs, background tasks, exports, and bulk operations can reach the same data. A carefully protected screen is of limited value if another entry point changes the record without the equivalent decision.
Make collection access explicit
Checking a single ticket does not settle how a list of tickets should be returned. Search results, counts, and exports may reveal information before someone opens a detail screen.
In the proposed application, the query should limit the accessible region before returning protected records. Fetching every region and hiding unwanted rows in the browser is not equivalent. For a bulk update, decide whether one denied record rejects the whole request or produces an explicit partial result. Either approach needs a defined outcome that callers can handle.
This is where a well-named policy earns its keep. It gives developers a shared point of discussion while still allowing read, list, and update operations to have different requirements.
Test changes in context
Access tests should include what happens after a person has already opened a page. The ticket may be reassigned. Their region may change. Another person may close the job before the update arrives.
Useful cases for this example include an owner editing an open ticket, the same owner editing a closed ticket, a supervisor working outside their region, and a direct request from someone whose screen control is hidden. These are suggested tests; no measured test-coverage percentage is claimed here.
Recheck the relevant conditions when the change is committed, using concurrency controls appropriate to the system. An earlier screen render is not evidence that the record is still in the same state.
Keep the design proportional
A small application with stable roles may not need a dynamic authorization dashboard. A larger application may benefit from centralized resource assignments and clearer policy boundaries. Both still need understandable rules and consistent enforcement.
The objective is not to invent an elaborate framework. It is to make access decisions reviewable and repeatable as the application grows. Roles remain useful, but they should not be asked to carry every relationship, exception, and workflow state by themselves.
For the wider application structure, see our low-code architecture guide. Good boundaries help the team find the rule, discuss it, and change it without guessing what else depends on it.
