.NET Web API Requests Authorization using EF Core And MediatR
Recently we came up with a scenario, where we have API requests representing actions like CRUD operations related to an customers. We want to ensure that the user initiating these requests has the necessary access permissions based on the customers being queried.
Currently, we are maintaining an application with over 100 CQRS classes handling various commands and queries. Integrating user access checks into each class individually can be time-consuming and error-prone, leading to code duplication and complexity.
We leveraged MediatR behaviors to implement global authorization checks for all CQRS requests. By intercepting requests at a centralized point in the pipeline, we can enforce access controls consistently across the application without modifying each individual class
We start by defining interfaces that represent MediatR requests and specialized customer-related requests:
The ICustomerRequest<TResponse> interface specifies that any request implementing it must provide a CustomerId property.
public interface ICustomerRequest<TResponse> : IRequest<TResponse>
{
Guid CustomerId { get; }
}
Next, we'll implement a MediatR behavior (CustomerRequestCommonBehavior) that intercepts requests, checks for authorization, and enforces access control:
In this behavior:
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (request is ICustomerRequest<TResponse> customerRequest)
{
var isAuthorized = await VerifyUserAuthorization(customerRequest.CustomerId);
if (!isAuthorized)
{
throw new AuthorizationException("Access denied for the user.");
}
}
var response = await next();
return response;
}
2. We use Entity Framework Core to verify if the current user is authorized to access the customer's information based on specific business logic.
Recommended by LinkedIn
private async Task<bool> VerifyUserAuthorization(Guid customerId)
{
var currentUser = await _customerDbContext.Users
.SingleOrDefaultAsync(u => u.EmailAddress == _userContextService.UserEmail);
if (currentUser != null)
{
var customerEntity = await _customerDbContext.Customers
.SingleOrDefaultAsync(c => c.Id == customerId);
if (customerEntity != null)
{
var isMember = await _customerDbContext.CustomerTeams
.Join(_customerDbContext.CustomerTeamMembers,
team => team.Id,
member => member.TeamId,
(team, member) => new { team, member })
.AnyAsync(t => t.team.CustomerId == customerEntity.CustomerId &&
t.member.UserId == currentUser.Id);
return isMember;
}
}
return false;
}
Finally, integrate the CustomerRequestCommonBehavior into the MediatR pipeline during setup:
var services = new ServiceCollection();
services.AddMediatR(
Assembly.GetAssembly(typeof(ApplicationDependencyInjection)),
Assembly.GetEntryAssembly());
services.AddTransient(
typeof(IPipelineBehavior<,>),
typeof(CustomerRequestCommonBehavior<,>));
Finally in the Query or Command class, we need can inherit ICustomerRequest interface. Now, on each request the pipeline will verify the user check to ensure secure access.
public static class GetCustomerByIdQuery
{
public record Request(Guid customerId) : ICustomerRequest <Response>;
public record Customer(int CustomerId, string FirstName, string LastName);
public record Response(Customer customer);
public class Handler : IRequestHandler<Request, Response>
{
public async Task<Response> Handle(Request request,
CancellationToken cancellationToken)
{
//Implement the code to get the customer details
}
}
}
By incorporating authorization checks directly into our MediatR request handling pipeline using Entity Framework Core, we ensure consistent access control across our application. This approach promotes clean separation of concerns and enhances security by preventing unauthorized access to sensitive customer data.
Implementing behaviors like CustomerRequestCommonBehavior demonstrates best practices and leverages the power of MediatR to design robust and secure software architectures.
I hope this article provides valuable insights into enhancing MediatR-based APIs with authorization checks using Entity Framework Core.
Original Code link:
Insightful!