dotnet-cqrs/docs/core-features/commands/command-authorization.md

4.4 KiB

Command Authorization

Secure your commands with authorization services.

Overview

Command authorization controls who can execute commands using the ICommandAuthorizationService<TCommand> interface.

Interface

public interface ICommandAuthorizationService<in TCommand>
{
    Task<bool> CanExecuteAsync(
        TCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken = default);
}

Basic Authorization

Authenticated Users Only

public class CreateUserAuthorizationService : ICommandAuthorizationService<CreateUserCommand>
{
    public Task<bool> CanExecuteAsync(
        CreateUserCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(user.Identity?.IsAuthenticated == true);
    }
}

// Registration
builder.Services.AddScoped<ICommandAuthorizationService<CreateUserCommand>, CreateUserAuthorizationService>();

Role-Based Authorization

public class DeleteUserAuthorizationService : ICommandAuthorizationService<DeleteUserCommand>
{
    public Task<bool> CanExecuteAsync(
        DeleteUserCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(user.IsInRole("Admin"));
    }
}

Advanced Authorization

Resource-Based Authorization

public class UpdateUserAuthorizationService : ICommandAuthorizationService<UpdateUserCommand>
{
    private readonly IUserRepository _userRepository;

    public UpdateUserAuthorizationService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public async Task<bool> CanExecuteAsync(
        UpdateUserCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken)
    {
        // Admins can update any user
        if (user.IsInRole("Admin"))
            return true;

        // Users can only update their own profile
        var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return command.UserId.ToString() == userId;
    }
}

Claims-Based Authorization

public class DeleteOrderAuthorizationService : ICommandAuthorizationService<DeleteOrderCommand>
{
    private readonly IOrderRepository _orderRepository;

    public async Task<bool> CanExecuteAsync(
        DeleteOrderCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken)
    {
        var order = await _orderRepository.GetByIdAsync(command.OrderId, cancellationToken);

        if (order == null)
            return false;

        // Check if user has required claim
        var canDelete = user.HasClaim("Permission", "DeleteOrder");
        if (!canDelete)
            return false;

        // Check if user owns the order
        var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return order.UserId.ToString() == userId;
    }
}

Multi-Tenant Authorization

public class CreateProductAuthorizationService : ICommandAuthorizationService<CreateProductCommand>
{
    public Task<bool> CanExecuteAsync(
        CreateProductCommand command,
        ClaimsPrincipal user,
        CancellationToken cancellationToken)
    {
        var userTenantId = user.FindFirst("TenantId")?.Value;

        if (string.IsNullOrEmpty(userTenantId))
            return Task.FromResult(false);

        // Ensure command is for user's tenant
        return Task.FromResult(command.TenantId == userTenantId);
    }
}

HTTP Responses

Unauthorized (401)

When user is not authenticated:

HTTP/1.1 401 Unauthorized

Forbidden (403)

When user is authenticated but not authorized:

HTTP/1.1 403 Forbidden

Combining with ASP.NET Core Authorization

var app = builder.Build();

// Require authentication for all endpoints
app.UseAuthentication();
app.UseAuthorization();

// CQRS endpoints inherit auth requirements
app.UseSvrntyCqrs();

Best Practices

DO

  • Use for access control
  • Check resource ownership
  • Validate tenant isolation
  • Log authorization failures
  • Return boolean (true/false)

DON'T

  • Don't throw exceptions
  • Don't perform business logic
  • Don't modify data
  • Don't bypass framework checks

See Also