Role-based and permission-based authorization with RequirePermission
FullStackHero implements a flexible authorization system that supports both role-based access control (RBAC) and permission-based authorization. The system uses ASP.NET Core’s authorization framework with custom handlers for fine-grained access control.
Permissions are defined as string constants in permission classes:
IdentityPermissionConstants.cs
public static class IdentityPermissionConstants{ public static class Users { public const string View = "identity.users.view"; public const string Create = "identity.users.create"; public const string Update = "identity.users.update"; public const string Delete = "identity.users.delete"; public const string Export = "identity.users.export"; } public static class Roles { public const string View = "identity.roles.view"; public const string Create = "identity.roles.create"; public const string Update = "identity.roles.update"; public const string Delete = "identity.roles.delete"; public const string ManagePermissions = "identity.roles.manage-permissions"; } public static class Groups { public const string View = "identity.groups.view"; public const string Create = "identity.groups.create"; public const string Update = "identity.groups.update"; public const string Delete = "identity.groups.delete"; }}
Use a hierarchical naming convention like module.resource.action for consistency and clarity.
The .RequirePermission() extension method is used to protect endpoints:
CreateGroupEndpoint.cs
public static RouteHandlerBuilder MapCreateGroupEndpoint( this IEndpointRouteBuilder endpoints){ return endpoints.MapPost("/groups", (IMediator mediator, [FromBody] CreateGroupCommand request, CancellationToken cancellationToken) => mediator.Send(request, cancellationToken)) .WithName("CreateGroup") .WithSummary("Create a new group") .RequirePermission(IdentityPermissionConstants.Groups.Create) .WithDescription("Create a new group with optional role assignments.");}
Roles are containers for permissions. Users are assigned roles, and roles have permissions.
public class Role{ public Guid Id { get; set; } public string Name { get; set; } = default!; public string? Description { get; set; } public ICollection<RolePermission> Permissions { get; set; } = [];}public class RolePermission{ public Guid RoleId { get; set; } public string Permission { get; set; } = default!;}
Users can have multiple roles, and their effective permissions are the union of all role permissions:
public class User{ public Guid Id { get; set; } public string Email { get; set; } = default!; public ICollection<UserRole> Roles { get; set; } = [];}public class UserRole{ public Guid UserId { get; set; } public Guid RoleId { get; set; } public Role Role { get; set; } = default!;}
Currently, the handler checks only the first permission. To require multiple permissions, extend the handler logic to check all permissions in the set.