Permissions are the easiest part of a management system to get wrong, because at the start it looks simple: a few roles, a few screens. The trouble arrives later, when the company adds branches and approval levels, and when someone asks why one department head can see another department's payroll.
Three layers of question to keep apart
Every permission system answers three different questions, and mixing them is the source of the trouble:
- Where can they go — which screens this person can open.
- What can they do — view, add, edit, delete, approve, export.
- On which data — only their own records, their department's, their branch's, or the whole company's.
The first two are usually built from the start. The third is often forgotten, and it is the most expensive to add late, because it touches every data query in the system.
Roles or fine-grained permissions
The most durable model has two layers: the system understands only fine-grained permissions, while roles are preset bundles of them for convenience.
- Permission checks in code always ask "does this user have approve rights on vouchers", never "is this user the chief accountant".
- When the company invents a new job title, you create a new role with the matching set — no code changes.
- Allow individual grants outside roles, because exceptions always exist. But you must be able to see who currently holds which individual grant.
Four levels of data scope
For multi-unit companies, design the scope ladder from the beginning, even if only one level is used today:
- Mine — records the person created or owns.
- My team or department — following the org tree, including the case where a department has sub-departments.
- My unit or branch.
- Whole system.
The important technical point: scope must be applied in the data query layer, not in the interface. Hiding a button does not stop someone who knows how to call the API directly.
Where permissions usually leak
The test: if someone copies a colleague's URL into their own browser, the system must refuse — not because the button is hidden, but because the server says no.
- Reports and exports. The list screen filters correctly, but the Excel export runs a different query without the filter.
- Global search. A system-wide search box very often reveals customer names or file names the user is not allowed to see.
- Notifications and email. The notification text accidentally contains information the recipient cannot view in the system.
- Attachment links. Files sit in storage with guessable paths and no permission check on download.
- Leftover rights after a transfer. Someone moves department and keeps the old permissions because nobody revoked them.
Design so you can audit later
Permissions are not only about blocking; they are about being able to explain. Things worth having from day one:
- A per-user permission view: what this person currently has, and which role it came from.
- A permission change log: who granted what, and when. It is the first thing asked for after a leak.
- An access log for sensitive data: who opened payroll, who exported the customer list.
- Automated tests for the important rules: each critical permission rule deserves a test asserting that an unauthorised user is refused.
A few operating principles
- Deny by default. Rights must be granted explicitly; a new endpoint without a permission attribute should be reachable by nobody rather than everybody.
- Few roles, named after the job. "Accounts payable clerk" is clearer than "Role 3".
- Review periodically. Every quarter, print the list of highest-privilege users and re-confirm it with their managers.
- Shared accounts are a blind spot. When several people use one login, every log entry loses its value.
Getting permissions right at the start costs a few extra days. Redoing them once the system is live costs far more, because every query written so far must be reviewed.




