70

I'm having trouble specifying two separate Authorization attributes on a class method: the user is to be allowed access if either of the two attributes are true.

The Athorization class looks like this:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class AuthAttribute : AuthorizeAttribute {
. . .

and the action:

[Auth(Roles = AuthRole.SuperAdministrator)]
[Auth(Roles = AuthRole.Administrator, Module = ModuleID.SomeModule)]
public ActionResult Index() {
    return View(GetIndexViewModel());
}

Is there a way to solve this or do I need to rethink my approach?

This is to be run in MVC2.

5 Answers 5

96

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://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles

Sign up to request clarification or add additional context in comments.

2 Comments

The OR examaple also works for .NET Framework (not core)!
It seems Roles are there, but not Policies (only Policy)
50

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

4 Comments

Would it be possible to create another Authorization attribute (i.e. public class MultipleAuthOrAttribute {) that would take as input several AuthAttributes?
You cannot create an Attribute that takes instances of other Attributes. Attribute class constructor can only accept compile-time valid values / primitive types. That's why AuthorizeAttribute takes Roles as a string.
Can someone provide a reference to official documentation that supports the statement that multiple AuthorizeAttribute instances are processed as a logical AND. In my own tests, it appears that the order of the filters is important, with the first filter having the final say of whether authentication succeeds.
See my answer for a way to get OR logic with multiple AuthorizationHandlers in asp.net core.
9

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

1 Comment

In NET Core 8 this wont work correctly. It will still use AND logic. Because HasFailed will be true, if at least one Requirement already called Fail().
5

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.

1 Comment

stackoverflow.com/questions/31464359/… This question also has some useful info that I have incorporated into our login process.
1

Override the DefaultAuthorizationEvaluator with this one to get it work with OR logic instead of default AND logic, to get it work on NET Core 8.

/// <summary>
/// Allow our API to have OR behavior with multiple authorization handlers,
/// in which the final authorization result is if ANY handlers return true.
/// This class was implemented because default behavior is to apply an AND behavior
/// with the result of each authorization handler.
/// </summary>
public class AppAuthorizationEvaluator : IAuthorizationEvaluator
{
    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
    {
        int totalRequirementsCount = context.Requirements.Count();

        // HasFailed will be true, if at least one requirement already called Fail()
        if (totalRequirementsCount <= 1)
        {
            return DefaultEvaluate(context);
        }

        var failedRequirements = context.PendingRequirements;

        bool isAllRequirementsFailed = totalRequirementsCount == failedRequirements.Count();

        if (isAllRequirementsFailed)
        {
            return DefaultEvaluate(context);
        }

        return AuthorizationResult.Success();
    }

    // Logic from DefaultAuthorizationEvaluator
    private static AuthorizationResult DefaultEvaluate(AuthorizationHandlerContext context)
    {
        return context.HasSucceeded
            ? AuthorizationResult.Success()
            : AuthorizationResult.Failed(context.HasFailed
                ? AuthorizationFailure.Failed(context.FailureReasons)
                : AuthorizationFailure.Failed(context.PendingRequirements));
    }
}

And register it in Program/Startup by DI:

services.AddSingleton<IAuthorizationEvaluator, CustomAuthorizationEvaluator>();

This is my working Answer inspired by ceres-rohana. Fixing his version to work in NET Core 8.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.