Authorization that scales: organizations, roles, and permissions
Almost every application starts authorization the same way: an 'is_admin' flag, then a 'role' column. It works right up until a customer asks for someone who can do most of what an admin does, but not one specific thing. If roles are the unit of truth, you are now inventing a new role for every combination — and the list never stops growing.
Permissions are the unit of truth
The fix is to make permissions the primitive and roles just named bundles of them. A permission is a single capability — 'listing.publish', 'claim.approve'. A role is a convenient grouping. Checks in code always ask about a permission, never a role, so adding a new kind of user is a data change, not a code change.
Membership is explicit
In a multi-tenant system, a person is not simply 'an admin'. They are a member of an organization, with a role in that organization. The same person can be an owner in one workspace and a read-only collaborator in another. Modeling membership explicitly — user, organization, and the role between them — is what makes that possible without contortions.
- Organization — the tenant boundary.
- Membership — the link that says 'this user belongs to this org, with this role'.
- Role → Permissions — the capabilities that membership grants.
Keep the check cheap
Authorization runs on every request, so it has to be fast. Carrying the necessary claims on the access token — scoped to the organization in context — avoids a database round-trip just to answer 'can this request do this?'. The token says what the user may do; the handler simply asks.
None of this is exotic, and that is the point. Boring, explicit authorization is the kind that survives real customers, audits, and the feature request that would have blown up a role-based shortcut.
Written by Elijah Soko
Get in touch