Key takeaways
- Separate roles (who someone is) from permissions (what an action requires).
- Add scopes — location, department, ownership — as a second dimension.
- Enforce access on the server for every request; the interface only hides what is not allowed.
- Log every change to roles and every sensitive access.
Access control often starts as an 'admin' flag and becomes a tangle of special cases. A structure for roles, permissions and scopes that stays understandable as the business grows.
How access control usually breaks
Most systems start with two kinds of users: administrators and everyone else. As the business grows, exceptions appear — a manager who can approve but not delete, a branch that should only see its own records, an auditor who can read everything and change nothing. Each exception becomes an if-statement, and soon nobody can say with confidence who can do what.
Roles and permissions are different things
Define permissions as the actions your system supports — create invoice, approve leave, export report. Define roles as named bundles of permissions that match real job functions. Code checks permissions, never role names.
This one rule makes change cheap. When a new job function appears, you create a role from existing permissions instead of editing code in dozens of places.
Add scope as a second dimension
Permissions answer 'can this person approve leave?'. Scopes answer 'for whom?'. In a hospital system, a department head may approve leave only for their department; in a multi-campus school, a principal sees only their campus.
- Organisational scope: company, region, branch, department.
- Ownership scope: records the user created or is assigned to.
- Relationship scope: a parent sees only their own children's records.
Enforce on the server, every time
Hiding a button is a usability feature, not a security control. Every API request must check permission and scope on the server, ideally in a shared layer so individual endpoints cannot forget. Where the database supports it, row-level policies add a further safety net.
Make access auditable
Record every change to roles and assignments, and every access to sensitive records. When a customer, auditor or regulator asks who could see what and when, the answer should come from a query, not from memory.
Enjoyed this? Get the next one by email.

