.NET Web API Requests Authorization using EF Core And MediatR

.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.

  • Understanding the Challenge

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.

  • Remediation Solution

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


  • Defining the IRequest and ICustomerRequest

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; }
}        

  • Implementing the CustomerRequestCommonBehavior

Next, we'll implement a MediatR behavior (CustomerRequestCommonBehavior) that intercepts requests, checks for authorization, and enforces access control:

In this behavior:

  1. We intercept MediatR requests and extract the CustomerId.


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.

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;
}
        

  • Enabling the Behavior in MediatR Pipeline

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
      }
  }
}        


  • Conclusion

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.

Article content

Original Code link:


To view or add a comment, sign in

More articles by Supriya Bhattacherjee

Others also viewed

Explore content categories