3.5 KiB
3.5 KiB
Query Authorization
Secure your queries with authorization services.
Interface
public interface IQueryAuthorizationService<in TQuery>
{
Task<bool> CanExecuteAsync(
TQuery query,
ClaimsPrincipal user,
CancellationToken cancellationToken = default);
}
Basic Authorization
Authenticated Users Only
public class GetUserAuthorizationService : IQueryAuthorizationService<GetUserQuery>
{
public Task<bool> CanExecuteAsync(
GetUserQuery query,
ClaimsPrincipal user,
CancellationToken cancellationToken)
{
return Task.FromResult(user.Identity?.IsAuthenticated == true);
}
}
// Registration
builder.Services.AddScoped<IQueryAuthorizationService<GetUserQuery>, GetUserAuthorizationService>();
Resource-Based Authorization
Own Data Only
public class GetUserAuthorizationService : IQueryAuthorizationService<GetUserQuery>
{
public Task<bool> CanExecuteAsync(
GetUserQuery query,
ClaimsPrincipal user,
CancellationToken cancellationToken)
{
// Admins can view any user
if (user.IsInRole("Admin"))
return Task.FromResult(true);
// Users can only view their own data
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return Task.FromResult(query.UserId.ToString() == userId);
}
}
Row-Level Security
public class ListOrdersAuthorizationService : IQueryAuthorizationService<ListOrdersQuery>
{
public Task<bool> CanExecuteAsync(
ListOrdersQuery query,
ClaimsPrincipal user,
CancellationToken cancellationToken)
{
// Ensure user can only see their own orders (enforced in query handler)
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
return Task.FromResult(false);
// Authorization passes - handler will filter by userId
return Task.FromResult(true);
}
}
// In handler:
public async Task<List<OrderDto>> HandleAsync(ListOrdersQuery query, CancellationToken cancellationToken)
{
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var orders = await _context.Orders
.Where(o => o.UserId.ToString() == userId) // Filter by user
.ToListAsync(cancellationToken);
return orders.Select(MapToDto).ToList();
}
Tenant Isolation
public class GetCustomerAuthorizationService : IQueryAuthorizationService<GetCustomerQuery>
{
public Task<bool> CanExecuteAsync(
GetCustomerQuery query,
ClaimsPrincipal user,
CancellationToken cancellationToken)
{
var tenantId = user.FindFirst("TenantId")?.Value;
if (string.IsNullOrEmpty(tenantId))
return Task.FromResult(false);
// Authorization passes - handler will filter by tenant
return Task.FromResult(true);
}
}
Best Practices
✅ DO
- Check resource ownership
- Validate tenant isolation
- Use for access control
- 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
HTTP Responses
- 401 Unauthorized - User not authenticated
- 403 Forbidden - User authenticated but not authorized