Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Authorization attributes on method

There is a better way to do this in later versions of asp.net you can do both OR and AND on roles. This is done through convention, listing multiple roles in a single Authorize will perform an OR where adding Multiple Authorize Attributes will perform AND.

OR example

[Authorize(Roles = "PowerUser,ControlPanelUser")] 

AND Example

[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]

You can find more info on this at the following link https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles


Multiple AuthorizeAttribute instances are processed by MVC as if they were joined with AND. If you want an OR behaviour you will need to implement your own logic for checks. Preferably implement AuthAttribute to take multiple roles and perform an own check with OR logic.

Another solution is to use standard AuthorizeAttribute and implement custom IPrincipal that will implement bool IsInRole(string role) method to provide 'OR' behaviour.

An example is here: https://stackoverflow.com/a/10754108/449906


I've been using this solution in production environment for awhile now, using .NET Core 3.0. I wanted the OR behavior between a custom attribute and the native AuthorizeAttribute. To do so, I implemented the IAuthorizationEvaluator interface, which gets called as soon as all authorizers evaluate theirs results.

/// <summary>
/// Responsible for evaluating if authorization was successful or not, after execution of
/// authorization handler pipelines.
/// This class was implemented because MVC default behavior is to apply an AND behavior
/// with the result of each authorization handler. But to allow our API to have multiple
/// authorization handlers, in which the final authorization result is if ANY handlers return
/// true, the class <cref name="IAuthorizationEvaluator" /> had to be extended to add this
/// OR behavior.
/// </summary>
public class CustomAuthorizationEvaluator : IAuthorizationEvaluator
{
    /// <summary>
    /// Evaluates the results of all authorization handlers called in the pipeline.
    /// Will fail if: at least ONE authorization handler calls context.Fail() OR none of
    /// authorization handlers call context.Success().
    /// Will succeed if: at least one authorization handler calls context.Success().
    /// </summary>
    /// <param name="context">Shared context among handlers.</param>
    /// <returns>Authorization result.</returns>
    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
    {
        // If context.Fail() got called in ANY of the authorization handlers:
        if (context.HasFailed == true)
        {
            return AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
        }

        // If none handler called context.Fail(), some of them could have called
        // context.Success(). MVC treats the context.HasSucceeded with an AND behavior,
        // meaning that if one of the custom authorization handlers have called 
        // context.Success() and others didn't, the property context.HasSucceeded will be
        // false. Thus, this class is responsible for applying the OR behavior instead of
        // the default AND.

        bool success = 
            context.PendingRequirements.Count() < context.Requirements.Count();

        return success == true 
            ? AuthorizationResult.Success()
            : AuthorizationResult.Failed(AuthorizationFailure.ExplicitFail());
    }
}

This evaluator will only be called if added to .NET service collection (in your startup class) as follows:

services.AddSingleton<IAuthorizationEvaluator, CustomAuthorizationEvaluator>();

In the controller class, decorate each method with both attributes. In my case [Authorize] and [CustomAuthorize].


I'm not sure how others feel about this but I wanted an OR behavior too. In my AuthorizationHandlers I just called Succeed if any of them passed. Note this did NOT work with the built-in Authorize attribute that has no parameters.

public class LoggedInHandler : AuthorizationHandler<LoggedInAuthReq>
{
    private readonly IHttpContextAccessor httpContextAccessor;
    public LoggedInHandler(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LoggedInAuthReq requirement)
    {
        var httpContext = httpContextAccessor.HttpContext;
        if (httpContext != null && requirement.IsLoggedIn())
        {
            context.Succeed(requirement);
            foreach (var req in context.Requirements)
            {
                context.Succeed(req);
            }
        }

        return Task.CompletedTask;
    }
}

Supply your own LoggedInAuthReq. In startup inject these in services with

        services.AddAuthorization(o => {
            o.AddPolicy("AadLoggedIn", policy => policy.AddRequirements(new LoggedInAuthReq()));
            ... more here
        });
        services.AddSingleton<IAuthorizationHandler, LoggedInHandler>();
        ... more here

And in your controller method

    [Authorize("FacebookLoggedIn")]
    [Authorize("MsaLoggedIn")]
    [Authorize("AadLoggedIn")]
    [HttpGet("anyuser")]
    public JsonResult AnyUser()
    {
        return new JsonResult(new { I = "did it with Any User!" })
        {
            StatusCode = (int)HttpStatusCode.OK,
        };
    }

This could probably also be accomplished with a single attribute and a bunch of if statements. It works for me in this scenario. asp.net core 2.2 as of this writing.