Traditional role-based security frequently leads to “Role Explosion”:
- Handlers cluttered with brittle attributes like
[Authorize(Roles = "Admin,SuperAdmin,Manager")], requiring sweeping code changes whenever a new business role is introduced; - Inability to enforce granular action controls (e.g. allowing reports to be viewed but forbidding export; allowing draft editing but forbidding publishing).
BitzOrcas.Modern implements a Resource-Action fine-grained authorization model: Request contracts declare IAuthorizedRequest, evaluated in microseconds by AuthorizationPipelineBehavior against the user’s dynamic permission matrix.
Resource-Action Authorization Lifecycle
Step 1: Declaring Resource and Action on Contracts
Have Commands or Queries implement IAuthorizedRequest:
using BitzOrcas.Application.Abstractions.Authorization;using BitzOrcas.Domain.Results;using Mediator;
namespace BitzOrcas.Finance.Contracts.Queries;
public sealed record ExportFinancialReportQuery( string ReportYear) : IQuery<Result<byte[]>>, IAuthorizedRequest{ // ① Target protected resource public ResourceDescriptor Resource { get; } = new("finance", "report");
// ② Specific business action required for this request public AuthorizationAction Action { get; } = AuthorizationAction.Export;}Step 2: Automated Pipeline Decision Guard
The pipeline evaluates decisions before entering business handlers:
using System.Threading;using System.Threading.Tasks;using BitzOrcas.Domain.Results;
public static class AuthorizationErrors{ public static readonly Error PermissionDenied = Error.Forbidden("Auth.PermissionDenied", "Current user is not authorized to execute the specified operation.");}
public sealed class AuthorizationPipelineBehavior<TRequest, TResponse>( IAuthorizationDecisionService decisionService, ICurrentUser currentUser) : IPipelineBehavior<TRequest, TResponse> where TRequest : IAuthorizedRequest{ public async ValueTask<TResponse> Handle( TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate<TRequest, TResponse> next) { // 1. Evaluate whether active user possesses permission for Resource + Action var isAuthorized = await decisionService.HasPermissionAsync( currentUser.UserId, message.Resource, message.Action, cancellationToken);
if (!isAuthorized) { // 2. Denied: short-circuit with 403 Forbidden return (TResponse)(object)Result.Failure( AuthorizationErrors.PermissionDenied.WithDescription( $"User is not authorized to execute {message.Resource.Name}:{message.Action}.")); }
// 3. Authorized: proceed to business handler return await next(message, cancellationToken); }}Summary
Resource-Action authorization decouples security from business handlers:
- Zero Hardcoded Roles: Code defines operations; roles and permissions are configured dynamically in the admin console;
- Granular Controls: Precise control over Create, Read, Update, Delete, Export, and Approve actions;
- Audit-Ready: Unauthorized attempts automatically generate security alerts.