In August 2026 I asked a simple question about one of our customers, a distribution company with several sales reps on the road. Earlier that summer, the owner had turned off the "discount" switch for all the reps. He did not want them giving discounts on their own. I wanted to know whether the switch was doing its job.
It was not. When we looked at the real data, hundreds of invoices created from the reps' own app carried a discount, and two of them had a discount of 100 percent. The goods left the warehouse for free. The switch had hidden the discount box on the rep's screen, and that was all it did. The server function that saved the sale accepted any discount it was given.
The owner was calm the whole time, because the switch said "off". That is the most dangerous kind of permission: one that looks like a lock and is really a curtain.
The short answer
A SaaS permission system has three ideas: a role that sets each person's ceiling, permission switches that can only lower that ceiling, and scope that decides which data a person works with. Hiding a button is user experience. Only a check inside the database is security, because anyone can skip the app and talk to the database directly.
Role, switch and scope
Every business app with more than one user must answer, for each person: what can they see, and what can they do? Three different ideas hide inside the word "permissions".
- Role. The person's job: cashier, accountant, warehouse keeper, sales rep. The role decides the maximum a person can ever do, its ceiling.
- Permission switch. A single on/off setting inside the role, such as "can give discounts". Owners use switches to fine-tune one employee without inventing a new role.
- Scope. Which data the person works with: which warehouses, cash boxes or customers.
Enforcement works like a bank. Taking the sign off a door hides the room. Locking the door stops people walking in. A guard inside stops someone who got in another way. The vault has its own lock in case everything else fails. In software those are four layers: hide the menu item, block the address, guard inside the screen, and lock in the database. The first three live in the app and can be bypassed. Only the fourth is security. In Supabase, that means Row Level Security (rules on each table deciding which rows a user may read or change) and checks inside your database functions.
A permission that exists only on the screen is a suggestion, not a rule.
How we did it in KashBoss
Switches that only narrow
In June 2026 we made one firm decision: a switch can only remove things inside the role. It can never add power above it. We stored only the exceptions. If the owner turns off sales for one accountant, the employee's record holds one note: "sales: off". A switch with no note follows the role. On the day we shipped, nothing changed for any existing employee. Decide for each new switch whether it is open or closed by default, on purpose, every time.
The switches that did nothing
We shipped the first version of switches and moved on. In July 2026 I switched off a section for an accountant and nothing happened. The sidebar on the web and the menu on mobile never read the switches at all. They only checked the subscription plan. Accountants at several companies had switches saved by their employers, and not one of them worked. The first version was a screen full of toggles with no enforcement behind it.
We rebuilt it in three app layers: one central function that answers "is this allowed?", menus that hide the item when the answer is no, and a guard on every route. Then we found more ways a switch dies quietly. A switch can be defined in the catalog but missing from a role's ceiling, so no owner ever sees it. A role can be given a bigger ceiling without matching menu items. And one line of logic decides a lot:
canEdit = isAdmin OR switchIsOn // wrong: every admin passes, the switch is ignored canEdit = isAdmin AND switchIsOn // right: the role decides who, the switch decides yes or no
With "OR", every manager passed before the switch was read. Worse, "admin" also included the manager role, so a switch meant to restrict managers was canceled for exactly the people it targeted. We now have an automatic test that fails if anyone writes "admin OR switch" again.
Put the lock in the database
The discount fix went into the server function that saves a rep's sale. Three details made it work. The function had an old and a new version, because older app copies on reps' phones still called the old one, so we guarded both. Allowed discounts still pass, because the owner had set automatic discount tiers on purpose, so the guard calculates what the rules allow and refuses only the extra discount typed by hand. And a rep who started the app offline worked with saved permissions, so the app now refreshes them as soon as the connection returns.
Nobody edits their own power
Two holes appear in almost every permission system. First, an employee who can open the employees screen can change roles, so an accountant allowed in could promote himself to manager. Second, we let owners restrict a branch manager to certain warehouses, but stored the restriction on the manager's own record, and branch managers could edit employees. A restricted manager could remove his own restriction, or create a brand-new employee with no restriction and log in as that. A database trigger, a small rule that runs automatically when a row changes, now blocks both. The owner is exempt.
Hiding is not locking
In September 2026 a customer's owner reported that branch managers could see sales from every branch, although he had limited each to certain warehouses. Our restriction filtered the list of warehouses in the picker, but the screen opened on "all warehouses" by default, and that case fetched the whole company. For any restriction, ask one question: if someone skipped the app and talked to the database directly, would this rule still hold? Finally, test with a real account holding the target role, trying the action before the owner grants it and after. Chapter 12 of the book has the full checklist.
Key takeaways
- A role sets the ceiling, and switches can only lower it for one person.
- Hiding a button is user experience. Only the database lock is security.
- Switches die quietly: missing from a ceiling, skipped by "OR", or never read by the menu.
- Nobody, at any level, should be able to edit their own power.
- Verify permissions with real accounts and real data, never by reading code alone.
Frequently asked questions
What is the difference between a role and a permission?
A role is a person's job and sets the maximum they can do. A permission switch is a single on/off setting inside that role, so an owner can narrow what one employee does without creating a new role.
Why isn't hiding a button enough for security?
Hidden buttons only change what the app shows. Anyone who calls the server or database directly can skip the screen, so the real check must run inside the database function that performs the action.
Should a new permission switch be open or closed by default?
It depends on the switch. Open by default means existing employees keep working with nothing missing. Closed by default fits sensitive features that nobody should use until the owner chooses. Decide deliberately and write down why.