Permission as View
Features the user has no permission for should not appear and then error out.
Features the user has no permission for should not appear and then error out. What the user sees should equal what they can do. Show things they can't access and then block them, and you're making them hit a wall every time.
What you'll run into:
- It only says "no permission" after you click, and you don't know who to ask to enable it
- The frontend hides the button, but the API doesn't gate, so a tweaked request gets through
- Adding a new role means editing a dozen pages, because permissions are hard-coded all over the place
Three kinds of permission
First sort out which kind it is—the implementation is completely different.
| Kind | What it controls | What it looks like in the UI |
|---|---|---|
| Page | Which pages you can see | Whether the nav item exists. No permission, no entry |
| Action | What you can do on a page | Whether the button renders: view, add, edit, delete, approve |
| Data | Which records you can see on the same page | The list shows different rows. Sales only sees their own customers |
The third kind is the easiest to miss. Page and buttons are gated, but the API returns all the data and the frontend just doesn't show it—that's where most privilege-escalation bugs come from.
Granularity can split one more layer: admin, edit, preview. Admin can assign permissions and delete data; edit can change content; preview can only view. Document products often add independent toggles for download, upload, and public sharing.
How to build RBAC
Don't bind permissions directly to users. Slip a role layer in between, and both adding people and adding permissions turn into configuration. A role is a bundle of permissions; users get roles, and departments or user groups can feed roles too. This is role-based access control (RBAC), which stays manageable when you have hundreds of users instead of binding rights one by one.rbac-wiki
Design it in three steps: list the roles first (by org structure or by system structure), then list the permissions (page, action, data), and finally draw a role-by-permission matching table. Once that table is drawn, most of the permission design is done.
The other model is ACL: keep a separate access list for each object. It's more precise and flexible, but every object needs its own list, so maintenance costs are much higher. It fits files, documents, and other cases that need per-item authorization.
How to handle it in the UI
- Don't show entries the user has no permission for. They don't appear in the nav; buttons aren't rendered. This is the default.
- Show-but-disable only in one case: the user knows the feature exists and can apply for it themselves. Then the disabled state needs a line like "contact your admin to enable"; without it, the state says nothing.
- The backend has to gate again. Hiding on the frontend is an experience decision, not a security one. Without API checks, a tweaked request crosses the line (see the security basics in understand tech).
- Filter data permission at the query layer. Not "fetch everything and filter on the frontend."
- Permission changes need feedback. The admin just granted you access; you have to refresh to see it—that's a good moment for a hint.
A note for AI
Put the permission rules into a project file so every API and every render has a basis.
## Permissions
Model: RBAC. User -> Role -> Permission. Never bind permissions directly to users.
Three kinds of permissions, all must be implemented:
- Page permissions: don't render the nav item without permission; also gate direct URL access
- Action permissions: don't render the button without permission; never render-then-disable
- Data permissions: filter in the query conditions; don't fetch everything and filter on the frontend
Granularity: admin / edit / preview.
Hard requirements:
- Hiding on the frontend is only an experience improvement. Every API must
check permissions independently; never rely on the frontend not sending requests.
- When adding any API, state which permission it needs before writing the implementation.
- For queries that involve data permissions, put the filter into the SQL or ORM query,
and explain the filtering basis in a code comment.
The exception (show-but-disable) applies only when the user knows the feature
exists and can apply for it themselves; the disabled state must include a line
telling them who to contact to enable it.
