Search code examples
asp.net-coreasp.net-core-webapiauthorize-attribute

Custom Authorization policy with multiple requirements not working when applied to controller action


I have an .NET Core 3 WebAPI where I want to use custom policy-based authorization, where one of the policies has multiple requirements.

I have added the policy in Startup.ConfigureServices() and added MyCustomPolicyHandler to DI:

public virtual void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy("MyCustomPolicy", policy =>
        {
            policy.Requirements.Add(new RequirementA());
            policy.Requirements.Add(new RequirementB());
            policy.Requirements.Add(new RequirementC());
            policy.Requirements.Add(new RequirementD());
            
        });
        
        services.AddScoped<IAuthorizationHandler, MyCustomPolicyHandler>();
        
    });                
}

I have implemented MyCustomPolicyHandler using the example from this documentation: Policy-based authorization in ASP.NET Core

public class MyCustomPolicyHandler: IAuthorizationHandler
{

    public Task HandleAsync(AuthorizationHandlerContext context)
    {
    
        var pendingRequirements = context.PendingRequirements.ToList();         
        
        Console.WriteLine($"MyCustomPolicyHandler: {pendingRequirements.Count} requirements");
        
        foreach (var requirement in pendingRequirements)
        {
            if(requirement is RequirementA) {
            
                // do stuff to check the requirements
                context.Succeed(requirement);
                
            }
            else if(requirement is RequirementB) {
                
                // do stuff to check the requirements
                context.Succeed(requirement);
            
            }
            else if(requirement is RequirementC) {
            
                // do stuff to check the requirements
                context.Succeed(requirement);
                
            }
            else if(requirement is RequirementD) {
                
                // do stuff to check the requirements
                context.Succeed(requirement);
            
            }
        }
        
        return Task.CompletedTask;
    
    }
}

Next, I apply the policy to one of the controller actions:

public class MyController 
{
    [HttpGet]
    [Authorize(Policy="MyCustomPolicy")]    
    public ActionResult<SomeViewModel> Get(int id) {
    
        // do stuff
        
        return Ok(vm);
    }

}

The problem I'm encountering is there are no requirements associated with the policy. The Console.WriteLine() message shows 0 requirements:

MyCustomPolicyHandler: 0 requirements

If I move the policy to the controller, the requirements are present:

[Authorize(Policy="MyCustomPolicy")]
public class MyController 
{
    [HttpGet]
    public ActionResult<SomeViewModel> Get(int id) {
    
        // do stuff
        
        return Ok(vm);
    }

}
MyCustomPolicyHandler: 4 requirements

However, I need this policy applied to the action, not the controller. The [Authorize(Policy="MyCustomPolicy")] attribute should work at the action level.

Any idea why this isn't working?

Thanks.


Solution

  • Because the place where MyCustomPolicyHandler is injected is misplaced. It needs to be placed outside of AddAuthorization.

    services.AddAuthorization(options =>
    {
         options.AddPolicy("MyCustomPolicy", policy =>
         {
             policy.Requirements.Add(new RequirementA());
             //...
         });          
    });
    
    services.AddScoped<IAuthorizationHandler, MyCustomPolicyHandler>();
    

    Result: enter image description here