TGolla.AspNetCore.Mvc.Filters 6.0.0

dotnet add package TGolla.AspNetCore.Mvc.Filters --version 6.0.0                
NuGet\Install-Package TGolla.AspNetCore.Mvc.Filters -Version 6.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TGolla.AspNetCore.Mvc.Filters" Version="6.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TGolla.AspNetCore.Mvc.Filters --version 6.0.0                
#r "nuget: TGolla.AspNetCore.Mvc.Filters, 6.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TGolla.AspNetCore.Mvc.Filters as a Cake Addin
#addin nuget:?package=TGolla.AspNetCore.Mvc.Filters&version=6.0.0

// Install TGolla.AspNetCore.Mvc.Filters as a Cake Tool
#tool nuget:?package=TGolla.AspNetCore.Mvc.Filters&version=6.0.0                

This package implements the code solution for a StackOverflow question on how to secure an API call with more than one policy.

StackOverflow https://stackoverflow.com/questions/51443605/how-to-include-multiple-policies

Question - How to include multiple policies

I have defined 2 policies, ADD and SUB as shown below.

options.AddPolicy("ADD", policy =>
    policy.RequireClaim("Addition", "add"));

options.AddPolicy("SUB", policy =>
    policy.RequireClaim("Substraction", "subs"));

All that I want to do is to include 2 policies on a controller method. How can I perform this operation.

 [Authorize(Policy = "ADD, SUB")]
 [HttpPost]
 public IActionResult PerformCalculation()
 {
 }

However, this gives me an error:

InvalidOperationException: The AuthorizationPolicy named: 'ADD, SUB' was not found

Answer

The first thing to realize is that the Authorize attribute Policy setting is singular unlike Roles which can be plural and that multiple policies are treated on an AND basis, unlike a list of roles which is treated on an OR basis.

In your example code “ADD, SUB” is considered a single policy name. If you want to attribute you method with both policies, your code should be as follows.

[Authorize(Policy = "ADD")]
[Authorize(Policy = "SUB")]
[HttpPost]
public IActionResult PerformCalculation()
{
}

However, this will not give you the effect you want of either or, since policies are AND together, hence both policies must pass to be authorized. Nor will the suggestions of writing a single policy or a requirements handler to handle the multiple requirements give you the result of treating policies on a OR basis.

Instead, the solution is to create a TypeFilterAttribute that accepts a list of policies and is tied to a IAsyncAuthorizationFilter that test for either or. The following outlines the two classes you will need to define and how to attribute your action method.

The following code defines the new attribute AuthorizeOnAnyOnePolicy class.

/// <summary>
/// Specifies that the class or method that this attribute is applied to requires 
/// authorization based on user passing any one policy in the provided list of policies.
/// </summary>
public class AuthorizeOnAnyOnePolicyAttribute : TypeFilterAttribute
{
    /// <summary>
    /// Initializes a new instance of the AuthorizeOnAnyOnePolicyAttribute class.
    /// </summary>
    /// <param name="policies">A comma delimited list of policies that are allowed to access the resource.</param>
    public AuthorizeOnAnyOnePolicyAttribute(string policies) : base(typeof(AuthorizeOnAnyOnePolicyFilter))
    {
        Regex commaDelimitedWhitespaceCleanup = new Regex("\\s+,\\s+|\\s+,|,\\s+",
                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);

        Arguments = new object[] { policies };
    }
}

The following code defines the authorization filter class which loops through and executes each policy in the list. Should all the policies fail the result of the authorization context is set to forbid.

public class AuthorizeOnAnyOnePolicyFilter : IAsyncAuthorizationFilter
{
    private readonly IAuthorizationService authorization;
    public string Policies { get; private set; }

    /// <summary>
    /// Initializes a new instance of the AuthorizeOnAnyOnePolicyFilter class.
    /// </summary>
    /// <param name="policies">A comma delimited list of policies that are allowed to access the resource.</param>
    /// <param name="authorization">The AuthorizationFilterContext.</param>
    public AuthorizeOnAnyOnePolicyFilter(string policies, IAuthorizationService authorization)
    {
        Policies = policies;
        this.authorization = authorization;
    }

    /// <summary>
    /// Called early in the filter pipeline to confirm request is authorized.
    /// </summary>
    /// <param name="context">A context for authorization filters i.e. IAuthorizationFilter and IAsyncAuthorizationFilter implementations.</param>
    /// <returns>Sets the context.Result to ForbidResult() if the user fails all of the policies listed.</returns>
    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        var policies = Policies.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
            
        // Loop through policies.  User need only belong to one policy to be authorized.
        foreach (var policy in policies)
        {
            var authorized = await authorization.AuthorizeAsync(context.HttpContext.User, policy);
            if (authorized.Succeeded)
            {
                return;
            }

        }
            
        context.Result = new ForbidResult();
        return;
    }
}

With the policies defined as shown in the question you would attribute the method as follows.

[AuthorizeOnAnyOnePolicy("ADD,SUB")]
[HttpPost]
public IActionResult PerformCalculation()
{
}

It’s that simple and you will find similar solutions in the following Stack Overflow questions.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on TGolla.AspNetCore.Mvc.Filters:

Package Downloads
TGolla.Swashbuckle.AspNetCore

This repository contains the following Swashbuckle addons. - Custom Ordering of Controllers - Adding Authentication/Authorization Information to Swagger API Documentation with Swashbuckle

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
6.0.0 5,334 4/12/2024
0.9.0 4,226 4/18/2023

This is an initial pre-release.