The Hidden Engineering Behind Role-Based Permissions
\"Add admin and member roles\" is a small requirement with a surprisingly large behavioural surface.
Contents
A product requirement often begins like this:
We need three roles: owner, admin and member.
That sounds small.
A coding agent can add a role column in minutes.
The difficult part of role-based access control in SaaS begins immediately afterwards.
Where does the role live?
In a simple single-user application, a role might live on the user.
In a multi-tenant SaaS product, that is often wrong.
A person can be:
owner of Organisation A
member of Organisation B
admin of Organisation C
The role belongs to a membership, not the global identity.
That one modelling decision affects:
- database schema;
- permission checks;
- invitations;
- organisation switching;
- tests.
Is a role the same thing as a permission?
A simple product can map roles directly to behaviour:
owner → everything
admin → most things
member → normal things
As the product grows, teams often need explicit permissions:
billing.manage
members.invite
members.remove
project.delete
settings.update
Then a role becomes a named set of permissions.
That raises more questions:
- Can customers define custom roles?
- Can permissions be added without migrating every row?
- Does the UI understand permission-level access?
- Can a role be changed while a user is logged in?
The capability has become a model, not one database field.
The final owner problem
Suppose the owner clicks "Leave organisation".
Should it work?
If they are the only owner, probably not.
Otherwise the organisation may become administratively orphaned.
Now consider:
- Can the final owner demote themselves?
- Can another admin remove the final owner?
- Can ownership be transferred?
- What happens if the final owner's user account is deleted?
These edge cases should be explicit.
Otherwise they become support incidents.
UI permissions are not security
Hiding an "Delete organisation" button is useful.
It is not authorisation.
Every privileged operation needs enforcement at the appropriate trusted boundary.
Depending on the architecture, that could live in:
- server handlers;
- database policies;
- service layer;
- middleware.
The important invariant is:
A client cannot gain a privileged effect merely by bypassing the UI.
A production RBAC implementation tests this directly.
Invitations cross permission boundaries
Invitations seem separate from roles.
They are usually connected.
When inviting a member:
- who can choose the role?
- can an admin invite another admin?
- can an admin invite an owner?
- can an old invitation be reused?
- what if the invited email already belongs to a member?
- what if the invitation role becomes invalid before acceptance?
A role system that ignores invitation behaviour is incomplete.
Revocation timing matters
If a user's permissions are reduced, when does the change take effect?
Potential answers include:
- next API request;
- next token refresh;
- next login;
- immediately through session invalidation.
Different architectures make different trade-offs.
The dangerous outcome is not choosing.
If a JWT contains stale role claims for hours, changing the database may not change the user's effective access.
That needs deliberate design.
Audit history should preserve historical truth
Suppose Alice was an admin when she deleted a project.
Later Alice becomes a normal member.
Should the audit log now display:
Alice, member, deleted Project X
?
Probably not if the role is intended to explain the historical action.
Some systems store the actor plus contextual metadata at the time of the event.
Others resolve identity dynamically.
Again, the right answer depends on the product.
The important part is recognising the decision.
Permissions are a good example of a reusable capability
This is exactly the kind of feature discussed in The Software Capability Is Becoming a New Unit of Development.
Most SaaS teams are not inventing a fundamentally new idea when they add:
- organisation roles;
- invitations;
- permission checks;
- owner safeguards.
The implementation must fit the local codebase.
The underlying engineering questions are highly reusable.
Useful acceptance tests
A robust permissions implementation might test behaviours such as:
member cannot call admin endpoint
admin can perform allowed admin action
admin cannot perform owner-only action
final owner cannot leave without transfer
revoked permission is no longer effective
invitation cannot assign a role inviter is forbidden to grant
permissions are scoped to the correct organisation
These tests express the capability more clearly than:
there is a role column
The visible feature is the smallest part
Users see:
Owner
Admin
Member
The production system underneath needs:
- a tenant-aware model;
- server-side enforcement;
- invitation rules;
- ownership invariants;
- revocation behaviour;
- tests.
This is the recurring pattern behind many "simple" SaaS features.
The interface is familiar.
The hidden engineering is where teams repeatedly spend the time.

