Article
Role-Based Access Control in Web Apps: Roles and Permissions
Almost every business web app eventually needs to answer the same question: who is allowed to do what? A sales assistant should see customer contacts but not salary data. A branch manager can approve leave for their team but not for other branches. An accountant can export invoices but should not change system settings.
Role-based access control, often shortened to RBAC, is the most common way to handle this. Users are assigned roles, and roles are granted permissions. It sounds simple, and the basic version is. The difficulty comes later, when the business grows and the exceptions start piling up. This guide explains how to design RBAC that stays manageable.
Roles, Permissions and Users
The core model has three parts:
- Permissions describe specific actions, such as view invoices, create invoices, approve refunds or manage users
- Roles group permissions that typically go together, such as Sales Staff, Finance or Branch Manager
- Users are assigned one or more roles
When a user tries an action, the app checks whether any of their roles includes the required permission. Code should check permissions, not role names. Checking "can approve refunds" is far more flexible than checking "is manager", because you can later grant that permission to another role without touching the code.
Designing Roles That Match the Business
Start by listing what different people actually do, not what their job titles are. Sit with the client or team and walk through daily tasks. You will often find that two job titles need the same permissions, or that one title covers very different responsibilities.
Keep the number of roles small at first. A handful of well-defined roles is easier to understand and audit than dozens of near-identical ones. Name permissions clearly and consistently, using a pattern like resource and action, so they remain readable as the list grows.
A useful exercise is to build a simple grid with roles across the top and permissions down the side, then tick what each role can do. Reviewing that grid with the client before coding catches misunderstandings early, and it becomes living documentation once the system is in use.
Handling Scope: Branches, Teams and Ownership
Pure RBAC answers "can this user approve leave?" but many apps also need "for whom?" A branch manager in Johor Bahru should approve leave for Johor staff, not Penang staff. This is about scope, and it is where many systems get messy.
Common approaches include:
- Attaching scope to the user, such as branch or department, and filtering data by it.
- Checking ownership, so users can edit records they created but only view others.
- Combining roles with simple attribute rules, such as region or record status.
Keep these rules in one central place, like policy classes or an authorisation service, rather than scattered across controllers and views. Our guide on authentication basics explains the login side that comes before authorisation.
Key takeaway: check permissions in code, keep the rules in one place and enforce them on the server. Hiding a button in the interface is not access control.
Enforce Rules on the Server
A common mistake is hiding menu items and buttons from users who lack permission, while the underlying API endpoints remain open. Anyone who knows the URL or inspects network requests can still perform the action. Every request that reads or changes data must be checked on the server, using the same permission logic.
The same applies to exports, reports, file downloads and background jobs. These often bypass normal controllers, so they need their own permission checks. Write automated tests for important cases, such as a regular staff member trying to access an admin endpoint.
Also return consistent responses when access is denied. Use a clear forbidden status for logged-in users who lack permission, avoid revealing whether a restricted record exists and log repeated denied attempts, which can signal someone probing the system.
Admin Tools and Audit Trails
Clients will want to manage roles themselves eventually. Provide a simple admin screen to assign roles to users, and be cautious about letting non-technical admins create arbitrary new permissions, since that can break assumptions in code. Protect the role management screen itself with a high-level permission.
Log important permission changes and sensitive actions: who granted a role, who approved a payment, who exported customer data. These logs help investigate problems and show clients that access is controlled, which matters for personal data protection.
FAQ
Can a user have more than one role?
Yes, and it is common. A person might be both Sales Staff and Report Viewer. The app combines permissions from all their roles.
Should I use a library for RBAC?
Often, yes. Most popular frameworks have well-tested packages for roles and permissions. They save time, but you still need to design sensible roles and check permissions consistently.
How often should roles be reviewed?
Review roles when the organisation changes, and periodically check who has powerful permissions. Remove access promptly when staff change jobs or leave.
Good access control quietly protects a business every day. Model permissions around real tasks, check them on the server, keep scope rules central and log sensitive changes, and your web app will stay secure and understandable as it grows.