Hiding a button is not a permission
Products accumulate capability. A portal that started as "manage your phone system" ends up able to reset credentials, reconfigure call routing, order hardware, change billing, and export data about colleagues. Most of that is added for administrators and, absent a reason not to, ends up visible to everyone.
Eventually somebody notices that a receptionist can see a button that would take down an office, and you are retrofitting an access model onto a system built without one.
Roles are not enough
The instinct is a handful of roles — admin, user, read-only — and it survives about a fortnight of contact with real customers.
Real organisations do not decompose that way. An office manager should administer users but not touch billing. Someone in finance needs invoices and nothing else. A partner's support staff need to act on customer accounts without being able to change the commercial arrangement. Every one of those is a legitimate combination and none of them is a point on a line from "can do little" to "can do everything".
So permissions end up granular and assignable individually: managing a provisioning blacklist, setting custom codes, seeing migration tooling, performing specific destructive actions. Roles, if you have them, become presets over that set rather than the model itself.
That is more work and it is the only thing that survives. The alternative is a permanent stream of requests to add another role that is mostly like an existing one.
Where permissions come from
A permission a user holds can arrive from several directions: granted directly, implied by their role, inherited from the account, or bestowed by a partner administering them. That is fine, and it means you need to be able to answer not just "can they" but "why", and to validate that the sources themselves are coherent.
Checking that permission sources are valid sounds like paranoia until you have a support conversation about why somebody can see something they should not, and the only available answer is that the system says so. An access model you cannot explain is one you cannot debug, and permission bugs are exactly the ones you get asked to explain.
The interface has to agree with the server
Here is the failure mode that gives the post its title.
The enforcement has to be server-side. That is not negotiable — anything else is not a permission, it is a suggestion. But if that is all you do, users see controls that fail when used, which is its own kind of broken: they do not conclude "I lack permission", they conclude the product is unreliable, and they raise a ticket.
So you need both, and the two must not drift. Every capability needs to be expressed twice — once as an enforced check and once as something the interface can ask about before rendering — and the second must be derived from the first rather than reimplemented next to it. Reimplementing is how you get a button that is hidden from people who could use it, and shown to people who cannot.
This extends further into the interface than expected. It is not only buttons: it is whole tabs, options within a form, a contact-add control that should not appear when the user has no directory they can write to, an account-creation link that should not exist in one brand's client. Each of those is a place the interface asserts something about capability, and each is a place it can be wrong.
Destructive actions deserve their own permission
Some capabilities are not "can this person edit" but "can this person do the irreversible thing".
The example I keep coming back to is scrambling an account's calling credentials — immediately invalidating them so that anything using them stops working. It is exactly the right thing to do if credentials have leaked, and it is a genuinely bad afternoon if done casually, because every device using them goes offline until reconfigured.
An action like that should not be bundled with general administration. It needs its own permission, granted deliberately to specific people, because the population who legitimately need it is much smaller than the population who can otherwise administer an account. The general lesson: severity is a dimension of permissions, not just subject area.
Not everything should be restricted
One change in this work went the other way: deliberately not restricting which users the user list returns.
It is worth including because the instinct during an access-control tightening is that more restriction is always safer. It is not. A phone system where you cannot see your colleagues is not a more secure phone system, it is a broken one — you cannot transfer a call to somebody you cannot find. Restricting that would have been defensible in a review and wrong in the product.
Access control is a product decision wearing security clothing. The question is never "is this sensitive" in the abstract; it is "who legitimately needs this to do their job", and the answer is frequently "everyone".
What I took from it
Granular from the start, roles as presets. Coarse roles feel simpler and generate an endless queue of near-duplicates. The flexible model is more work once; the rigid one is more work forever.
Enforce once, expose the same answer to the interface. Two implementations of the same rule will diverge. Let the interface ask the server what the user can do rather than working it out independently.
Separate severity from subject. The right split is often not "billing versus users" but "reversible versus not". Irreversible actions want their own grants regardless of which area they belong to.
Restriction has a cost, and it lands on people doing their jobs. During a tightening exercise, every proposed restriction should have to answer who it protects and who it obstructs. Sometimes the honest answer is that it obstructs everyone and protects nobody.
Also in this series
- The tool your colleagues live in — maintaining the portal a company runs on
- One person, several accounts — SSO when identity isn't one-to-one
- Reports are a scheduling problem — building call reporting end to end
- A catalogue is a query — selling hardware and addons through partners
- Decommissioning is the milestone — replacing the helpdesk support lives in