Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mediatr 3.0 Using Pipeline behaviors for authentication

Tags:

c#

mediatr

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
    }
}
like image 854
Betty Avatar asked Jan 09 '17 01:01

Betty


People also ask

What is MediatR pipeline?

Before moving to my last upgrade, I need to explain what MediatR Pipeline is. It is a pipeline defined for one MediatR Request (message). Pipelines execution starts with Mediator. Send method invocation and passing an object of MediatR Request into it.

What is pipeline behavior?

The 'IPipelineBehavior' exactly works like Asp.Net Core middleware, but its starting execution and ending execution happens within the 'IMediator'. So the 'IMediator. send()' is usually used to invoke the 'Query' or 'Command' handlers.

What is MediatR .NET core?

MediatR Requests are very simple request-response style messages, where a single request is synchronously handled by a single handler (synchronous from the request point of view, not C# internal async/await). Good use cases here would be returning something from a database or updating a database.


3 Answers

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.

like image 70
Mickaël Derriey Avatar answered Oct 05 '22 23:10

Mickaël Derriey


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.

like image 33
Jamie Hollyhomes Avatar answered Oct 06 '22 00:10

Jamie Hollyhomes


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.

like image 35
Ben Martin Avatar answered Oct 06 '22 00:10

Ben Martin