Authorization
Ramsha provides a flexible and extensible authorization system that integrates seamlessly with ASP.NET Core [Authorize] attributes, while supporting custom permission value resolvers and multiple providers.
Permission Definitions
Permissions in Ramsha are defined using the IPermissionDefinitionProvider interface. Each permission belongs to a group and can have child permissions to form a hierarchical structure.
Example
public static class ProductPermissions
{
public const string GroupName = "products";
public static class Manage
{
public const string Default = $"{GroupName}:manage";
public const string Create = $"{GroupName}:manage:create";
public const string Update = $"{GroupName}:manage:update";
public const string Delete = $"{GroupName}:manage:delete";
}
}
public class ProductPermissionsDefinition : IPermissionDefinitionProvider
{
public void Define(IPermissionDefinitionContext context)
{
context.Group(ProductPermissions.GroupName, g =>
{
g.Permission(ProductPermissions.Manage.Default).Children(c =>
{
c.Add(ProductPermissions.Manage.Create);
c.Add(ProductPermissions.Manage.Update);
c.Add(ProductPermissions.Manage.Delete);
});
});
}
}
GroupNameorganizes related permissions- Permissions can have children, creating hierarchical permission structures
- Each permission can specify providers, which control which resolvers are executed
Checking Permissions
Permissions can be checked manually using IPermissionChecker:
public class ProductAppService(IPermissionChecker permissionChecker)
{
public async Task<bool> CanUserCreateProductAsync(ClaimsPrincipal? user)
{
return await permissionChecker.IsAssignedAsync(user, ProductPermissions.Manage.Create);
}
}
- Useful for conditional UI rendering, background jobs, or programmatic logic
IPermissionCheckerinternally calls the resolver system for permission evaluation
Permission Value Resolvers
Resolvers determine whether a permission is granted. Each resolver returns a PermissionStatus:
Allow→ permission granted, stop further evaluationDeny→ permission denied, stop further evaluationNone→ continue to the next resolver
Only resolvers whose provider name matches the permission's Providers list are executed.
Built-in Resolver Example
public class UserPermissionValueResolver(IPermissionStore store)
: PermissionValueResolver("U")
{
public override async Task<PermissionStatus> ResolveAsync(PermissionResolveContext context)
{
var userId = context.Principal?.FindUserId();
if (userId is null) return PermissionStatus.None;
var isAssigned = await store.IsAssignedAsync(
context.Permission.Name, GetProviderName(), userId);
return isAssigned ? PermissionStatus.Allow : PermissionStatus.None;
}
}
"U"is the provider name for this resolver- Only permissions specifying this provider will execute this resolver
Custom Resolver Example
public class RoleBasedPermissionResolver(IRoleStore roleStore)
: PermissionValueResolver("R")
{
public override async Task<PermissionStatus> ResolveAsync(PermissionResolveContext context)
{
var userId = context.Principal?.FindUserId();
if (userId is null) return PermissionStatus.None;
var hasRolePermission = await roleStore.HasPermissionAsync(userId, context.Permission.Name);
return hasRolePermission ? PermissionStatus.Allow : PermissionStatus.None;
}
}
"R"ensures this resolver is applied only for permissions using that provider- You can create multiple custom resolvers for different scenarios
Permission Status
public enum PermissionStatus
{
None = 0, // Not determined yet
Allow = 1, // Granted
Deny = 2 // Explicitly denied
}
- Resolvers are executed in order
- The first resolver to return
AlloworDenystops further evaluation - If all resolvers return
None, the final status isDeny - This ensures predictable and consistent permission evaluation
Registering Permission Value Resolvers
Resolvers are registered during the configuration phase:
context.Configure<RamshaPermissionOptions>(options =>
{
options.ValueResolvers.Add<UserPermissionValueResolver>();
// Control execution order
options.ValueResolvers.AddBefore<UserPermissionValueResolver, RoleBasedPermissionResolver>();
});
- The order of resolvers is important
- Only resolvers whose provider matches the permission are executed
- You can use
AddBeforeorAddAfterto control execution order - Custom resolvers can be added for advanced scenarios
Using [Authorize] Attribute
Permissions can be enforced declaratively on controllers or endpoints:
public class ProductsController : RamshaApiController
{
[Authorize(Permission = ProductPermissions.Manage.Create)]
[HttpPost("create")]
public async Task<IActionResult> CreateProduct()
{
return Ok();
}
}
[Authorize]internally usesIPermissionCheckerand the resolver system- Ensures consistent permission evaluation across manual checks and attribute-based authorization
- Works with hierarchical permissions and custom resolvers
- Respects the provider filtering for each resolver