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