History and context
Roles are management; permissions are capability
A role such as SHOP_ADMIN is a convenient bundle. The actual application decision is usually a permission such as PRODUCT_UPDATE or ORDER_REFUND.
A simple relational model
s_role
s_permission
s_role_permission
app_menu
app_menu_permissionA menu can require one or more permissions. The backend should also expose the user's effective permissions for the active operating context.
What belongs in the token?
Stable identity and coarse claims may fit naturally in an access token. Large, rapidly changing permission sets can make tokens stale and oversized. Many systems therefore keep access tokens relatively compact and resolve effective permissions through a short-lived cache or dedicated authorization service.
Frontend menu matching
const canAccessMenu = (
requiredPermissions: string[],
userPermissions: Set<string>
) =>
requiredPermissions.length > 0 &&
requiredPermissions.every(p => userPermissions.has(p));Backend enforcement
[Authorize(Policy = "PRODUCT_UPDATE")]
[HttpPut("products/{id:long}")]
public async Task<IActionResult> Update(
long id,
UpdateProductRequest request)
{
await service.UpdateAsync(id, request);
return NoContent();
}One active role can simplify business context
If the application intentionally operates with one active role or site context at a time, resolve that context during login or context selection and calculate effective permissions for it. This is often easier to reason about than mixing permissions from unrelated roles across locations.
Business value and practical considerations
Strengths
- Central permission vocabulary
- Consistent menu behaviour
- Server-side security
- Reusable across web/POS/admin
Considerations
- Permission lifecycle management
- Token/cache freshness
- Multi-tenant context
- Nested menu inheritance rules
References
Primary documentation used for terminology and current platform guidance: