3

I have an application written with C# on the top on ASP.NET Core 2.2 framework.

I want to be able to check if a user has a claim before I allow them access to the action.

I created an AuthorizationHandler to check if the user has the claim like so

public class ClaimExistanceHandler : AuthorizationHandler<MustHaveClaimRequirement>
    {
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MustHaveClaimRequirement requirement)
        {
            if (context == null
                || context.User == null
                || context.User.Identity == null
                || !context.User.Identity.IsAuthenticated
                || requirement == null
                || string.IsNullOrWhiteSpace(requirement.Type)
                || context.User.HasClaim(requirement.Type, requirement.Value))
            {
                context.Fail();

            }
            else
            {
                context.Succeed(requirement);
            }

            await Task.Yield();
        }
    }
}

then the requirement is as follow

public class MustHaveClaimRequirement : IAuthorizationRequirement
{
    public string Type { get; set; }
    public string Value { get; set; }

    public MustHaveClaimRequirement(string type, string value)
    {
        Type = type;
        Value = value;
    }
}

But how can I call this requirement as an attribute? For example HasPermission("do something", "1")

It seems that my HasPermission class needs to implement the AuthorizeAttribute but not sure how would I call the handler from the attribute.

Junior
  • 9,853
  • 16
  • 76
  • 173
  • hmm... putting aside the provided answers on Policies; It seems like you're asking how to inherit the `AuthorizeAttribute` and `Override` the base methods. Is that what you're asking about here? – Brett Caswell Jun 16 '19 at 22:48
  • See this question and answer: https://stackoverflow.com/a/31465227/1366179 (feel free to review the comments too) – Brett Caswell Jun 16 '19 at 22:58

3 Answers3

4

Your primary goal here is to get the requirement into a policy, and then use or create an attribute that can specify that policy with a string name. Once you do that, don't need to worry about calling the handler yourself, because ASP.NET Core will take care of that for you.

The simplest method of creating policies is to do it on app startup, as documented here. You create your policies, then use AuthorizeAttribute to specify which policy to attach to each endpoint.

However, doing it this way requires you to define all your policies up front. If that would result in you needing to create tons of different policies (because you are going to be checking lots of different claim types), and what you really want is to be able to have an attribute that specifies the claim information, there is a more dynamic way of doing it: see here. You need to create an AuthorizeAttribute implementation that stuffs your parameter values (name and type) into a string, and create and register an IAuthorizationPolicyProvider that can interpret that string and generate a policy with the appropriate requirement.

EDIT: It's also worth pointing out that ASP.NET Core already includes a requirement implementation for checking a claim: ClaimsAuthorizationRequirement. AuthorizationPolicyBuilder has a shortcut for it (RequireClaim) so you can quickly create policies that check claims:

services.AddAuthorization(options =>
    {
        options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    });
nlawalker
  • 5,654
  • 6
  • 26
  • 43
2

First, you need to register policy and their related claims:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddAuthorization(options =>
    {
        //Scenario 0: Policy requires Claim0 without care what the value is
        options.AddPolicy("MyPolicy0", policy => policy.RequireClaim("Claim0"));

        //Scenario 1: Policy requires Claim1 with value ClaimValue1_1 OR ClaimValue 1_2
        options.AddPolicy("MyPolicy1", policy => policy.RequireClaim("Claim1", "ClaimValue1_1", "ClaimValue1_2"));

        //Scenario 2: Policy requires Claims2 AND Claim3 with particular values
        options.AddPolicy("MyPolicy2", policy => {
            policy.RequireClaim("Claim2", "ClaimValue2");
            policy.RequireClaim("Claim3", "ClaimValue3"));
        }

        //Scenario 3: Policy requires Claims4 OR Claim5 with particular values
        options.AddPolicy("MyPolicy3", policy => {
            policy.RequireAssertion(ctx =>
            {
              return ctx.User.HasClaim("Claim4", "ClaimValue4") ||
                     ctx.User.HasClaim("Claim5", "ClaimValue5");
            })
        }
    });
}

Then apply these checks whenever you need (can be applied at controller or action level):

[Authorize(Policy = "Policy1")]
public class HomeController : Controller
{

    [Authorize(Policy = "Policy2")]
    public ActionResult MyAction()
    {
       ...
    }

    [Authorize(Policy = "Policy3")]
    public ActionResult MyAnotherAction()
    {
       ...
    }

    [AllowAnonymous]
    public ActionResult NotSecuredAtAll()
    {
       ...
    }
}

Don't forget, if you apply multiple policies to a controller or action, then all policies must pass before access is granted (if only they don't have AllowAnonymous attribute).

More about claims-based authorization in ASP.NET Core 2.2

ivamax9
  • 2,435
  • 20
  • 32
1

I managed to write up the attribute which allows checking for claim existence after taking the feedback from nlawalker and ivamax9

In conclusion, the HasPermissionAttribute class takes claimType and an optional claimValue then creates a policy name. The attribute class looks like this

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class HasPermissionAttribute : AuthorizeAttribute   
{
    public const string Policy_Prefix = "HasClaim";
    public const string Policy_Glue = ".";

    public HasPermissionAttribute(string type, string value = null)
    {
        Policy = GetPolicyValue(type, value);
    }

    private string GetPolicyValue(string type, string value)
    {
        if (string.IsNullOrWhiteSpace(type))
        {
            throw new ArgumentNullException($"{type} cannot be null.");
        }

        List<string> parts = new List<string> { type.Replace(Policy_Glue, "_").Trim() };

        if (!string.IsNullOrWhiteSpace(value))
        {
            parts.Add(value.Replace(Policy_Glue, "_"));
        }

        string policy = $"{Policy_Prefix}{Policy_Glue}{string.Join(Policy_Glue, parts)}";

        return policy;
    }
}

Now that we have a policy being applied via the HasPermissionAttribute we now need to take the applied policy and register it using AuthorizationPolicyBuilder which check if the given claim exists or not. That said, I added a class called ClaimCheckerPolicyProvider which takes the provided claim and processing the check as follow

internal class ClaimCheckerPolicyProvider : IAuthorizationPolicyProvider
{
    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
    }

    public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        if (IsClaimBasePolicy(policyName))
        {
            string[] parts = GetParts(policyName);

            if (parts.Length > 0)
            {
                AuthorizationPolicyBuilder policy = GetPolicyBuilder(parts);

                return Task.FromResult(policy.Build());
            }
        }

        return Task.FromResult<AuthorizationPolicy>(null);
    }

    private bool IsClaimBasePolicy(string policyName)
    {
        return !string.IsNullOrWhiteSpace(policyName)
                        && policyName.StartsWith(HasPermissionAttribute.Policy_Prefix, StringComparison.OrdinalIgnoreCase);
    }

    private string[] GetParts(string policyName)
    {
        return policyName.Split(HasPermissionAttribute.Policy_Glue, StringSplitOptions.RemoveEmptyEntries)
                         .Where(x => !x.Equals(HasPermissionAttribute.Policy_Prefix))
                         .ToArray();
    }

    private AuthorizationPolicyBuilder GetPolicyBuilder(string[] parts)
    {
        if (parts == null)
        {
            throw new ArgumentNullException($"{nameof(parts)} cannot be null.");
        }

        var length = parts.Length;

        if (length == 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(parts)} cannot cannot be empty.");
        }

        var policy = new AuthorizationPolicyBuilder();

        if (length > 1)
        {
            return policy.RequireClaim(parts[0], parts[1]);
        }

        return policy.RequireClaim(parts[0]);
    }
}

Finally, we need to register the provider as a service. In the ConfigureServices of the Startup class, we add the following

services.AddTransient<IAuthorizationPolicyProvider, ClaimCheckerPolicyProvider>();
Junior
  • 9,853
  • 16
  • 76
  • 173