Mediatr 3.0 Using Pipeline behaviors for authentication

10.7k views Asked by At

Looking at using the new Mediatr 3.0 feature pipeline behaviors for authentication/authorization.

Would you normally auth based on the message or the handler? reason I'm asking is that I'd auth on the handler (same as controller in MVC) but behaviors don't appear to have knowledge about the handler so I'm not sure this is possible/suitable.

I could add an IAuthorisationRequired marker interface to each message, but if the message is a notification/event and has multiple handlers then maybe some should run but not others. Really does feel better checking auth on the handler code that does the actual work.

Would love to be able to put a [Authorize] attribute on a handler and user a behaviour to check it (I currently do exactly this but with a base class instead of a behaviour).

public class AuthenticationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        //Can't access handler class here, so how do I know the action requires authentication/authorization?
        return next();
    }
}

[Authorize]
public class ChangePasswordRequestHandler : IAsyncRequestHandler<ChangePassword, ReponseType>
{   
    protected override async Task<ReponseType> Handle(AsyncRequestBase<ChangePassword> message)
    {
        //change users password here
    }
}
3

There are 3 answers

3
Mickaël Derriey On BEST ANSWER

You're right, the RequestDelegateHandler<TResponse> doesn't expose what handler will run next, and this is intentional. If you think about it, pipelines in MediatR 2.x used decorators, and while the decorator had access to the instance of the decoratee, I would advise against doing auth based on it. The reason is that if you need your authorization decorator to decorate one specific instance of a handler - the one decorated with specific attributes - then they're coupled, which defeats the purpose of decorators where you should be able to put them on top of each other independently.

That's why I would advise basing authorization on the message, at least in most cases. You could have an extensible design where to each message are associated several authorization rules, and a behavior evaluates all of them.

public interface IAuthorizationRule<TRequest>
{
    Task Evaluate(TRequest message);
}

public class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly IAuthorizationRule<TRequest>[] _rules;

    public AuthorizationBehavior(IAuthorizationRule<TRequest>[] rules)
    {
        _rules = rules;
    }

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        // catch it or let it bubble up depending on your strategy
        await Task.WaitAll(_rules.Select(x => x.Evaluate(request)));

        return next();
    }
}

For the specific case you mention where, for a notification, some handlers might run while others shouldn't, you can always use authorization behaviors that target that specific message and apply them selectively to the handlers that need them. I guess my point is you'll have to do a bit of crafting yourself when you hit those specific scenarios.

1
Jamie Hollyhomes On

I had the same requirement for a project and implemented a specific pipeline where I could inject (if required) a AuthorisationHandler for a specific request. This means I just need to add a new AuthorisationHandler for each new command that I created, and then it will be called before the request to process the actual command.

The pipeline:

public class Pipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>
{
    private readonly IAuthorisationHandler<TRequest, TResponse>[] _authorisationHandlers;
    private readonly IAsyncRequestHandler<TRequest, TResponse> _inner;
    private readonly IPostRequestHandler<TRequest, TResponse>[] _postHandlers;

    public Pipeline(IAuthorisationHandler<TRequest, TResponse>[] authorisationHandlers, IAsyncRequestHandler<TRequest, TResponse> inner, IPostRequestHandler<TRequest, TResponse>[] postHandlers)
    {
        _authorisationHandlers = authorisationHandlers;
        _inner = inner;
        _postHandlers = postHandlers;
    }

    public async Task<TResponse> Handle(TRequest message)
    {
        foreach (var authorisationHandler in _authorisationHandlers)
        {
            var result = (ICommandResult)await authorisationHandler.Handle(message);

            if (result.IsFailure)
            {
                return (TResponse)result;
            }
        }

        var response = await _inner.Handle(message);

        foreach (var postHandler in _postHandlers)
        {
            postHandler.Handle(message, response);
        }

        return response;
    }
}

The Authorsiation Handler:

 public class DeleteTodoAuthorisationHandler : IAuthorisationHandler<DeleteTodoCommand, ICommandResult>
{
    private IMediator _mediator;
    private IAuthorizationService _authorisationService;
    private IHttpContextAccessor _httpContextAccessor;

    public DeleteTodoAuthorisationHandler(IMediator mediator, IAuthorizationService authorisationService, IHttpContextAccessor httpContextAccessor)
    {
        _mediator = mediator;
        _authorisationService = authorisationService;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<ICommandResult> Handle(DeleteTodoCommand request)
    {
        if (await _authorisationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, "DeleteTodo"))
        {
            return new SuccessResult();
        }

        var message = "You do not have permission to delete a todo";

        _mediator.Publish(new AuthorisationFailure(message));

        return new FailureResult(message);
    }
}

My AuthorisationHandler implemements IAuthorisationHandler which looks like this:

    public interface IAuthorisationHandler<in TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>
{
    Task<TResponse> Handle(TRequest request);
}  

It then hangs together using the DecorateAllWith (part of structuremap)

cfg.For(typeof(IAsyncRequestHandler<,>)).DecorateAllWith(typeof(Pipeline<,>));

Not sure you should do this for 3.x as this now has a new pipeline interface

IPipelineBehavior<TRequest, TResponse>

Not used it yet but I think it will simplify the implementation and mean you can stop using the decorator pattern DecorateAllWith.

3
Ben Martin On

You could do this in the same way I use Fluent Validation.

I created the following behaviour:

namespace MediatR.Extensions.FluentValidation
{
    public class ValidationPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    {
        private readonly IValidator<TRequest>[] _validators;

        public ValidationPipelineBehavior(IValidator<TRequest>[] validators)
        {
            _validators = validators;
        }

        public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
        {
            var context = new ValidationContext(request);

            var failures =
                _validators.Select(v => v.Validate(context)).SelectMany(r => r.Errors).Where(f => f != null).ToList();

            if (failures.Any())
            {
                throw new ValidationException(failures);
            }

            return await next();
        }
    }
}

Create a AbstractValidator

 public classs SaveCommand: IRequest<int>
 {
    public string FirstName { get; set; }

    public string Surname { get; set; }
 }

   public class SaveCommandValidator : AbstractValidator<SaveCommand>
    {
       public SaveCommandValidator()
       {
          RuleFor(x => x.FirstName).Length(0, 200);
          RuleFor(x => x.Surname).NotEmpty().Length(0, 200);
       }
    }

So you could create a Authorization<T> class where you could add your custom authorization code per request and have injected into a AuthorizationPipelineBehavior<TRequest, TResponse> class.