Engineering notes Note
Brainex Thera Argus Security · Backend · Architecture
Role-aware UI is not authorization
Hiding a page from a role is good UX and no protection at all. Where the real checks live in a multi-tenant system, why they should fail closed, and how to test the boundaries rather than the buttons.
Every system I’ve worked on recently shows people only what their role allows. Argus scopes its dashboards to each user’s role. Thera’s store ERP has eleven staff roles, each limited to its own area. BrainexOne applies permissions across many modules and tenants.
That is good interface design. It is not authorization. A hidden button is still an open endpoint to anyone holding a valid token and a terminal.
Where the decision actually lives
The interface decides what is convenient. The server decides what is allowed — on every request, before any business logic runs. In Thera, a request to the store API passes a chain like this:
- Request arrives with a token
- Authenticate on the right plane — store, customer and platform tokens are separate · else 401
- Scope to the store named in the token — never one from the body, query or path · else 403
- Check the role for this action · else 403
- Check the plan for paid features and caps · else 402
- Run the handler — sensitive actions re-check in the database
Two details carry most of the weight.
Scope comes from the token, not the request. If the store a request acts on could come from a URL or a body field, every endpoint would have to remember to compare it with the user’s store. Taking it only from the token makes a forged store id structurally irrelevant.
Sensitive actions re-check the source of truth. Publishing a storefront is owner-only. The role in the token says “owner”, but the handler still re-reads the store’s actual owner from the database before acting.
Fail closed
The most useful property of an authorization layer is what it does when someone forgets something.
That last one was a real change. “No permission declared” used to mean “any platform admin may call this” — so forgetting one line silently granted a route to every admin role, read-only auditors included. Now the guard refuses it, and a test scans every admin handler for its full guard chain. The test also fails if the scan finds no handlers at all, so it can’t pass by accident.
Revocation follows the same idea. Tokens carry a version for the user and for the store, and both are compared on every request. Suspending a store or freezing a user takes effect on the next request, not when the token expires.
The same habits in an enterprise platform
In BrainexOne I build CRM and HSEQ features where tenant isolation, permissions and audit history are part of the workflow, and I contributed permission-system capabilities, role-based authorization and access-management testing to the platform. Without going into its implementation, the habits that carry over are:
- Permissions are part of the feature. A workflow isn’t done when the screen works; it’s done when the wrong role can’t perform it through the API.
- Tenant boundaries belong in every read and write path, not in where the user happened to click.
- Changes are attributable. Audit history records who changed what, which matters as much for support as for security.
Test the boundaries, not the buttons
UI tests show that a button is hidden. They say nothing about the endpoint behind it. The tests that matter call the API as the wrong identity:
- Plane isolation: in Thera, a store user can’t sign in to the platform console, and the refusal is a generic 401 that doesn’t reveal the account exists.
- Entitlements: a matrix of plans and features, including a store with no plan at all, which must fail closed.
- Route coverage: the guard-chain scan above, so a new admin route can’t ship without its checks.
At Brainex, access-management testing is part of the permission work itself.