How to work with Policies in Identity Membership System

How to work with Policies in Identity Membership System

Just like you did the Authentication based on Roles, in the same way, you can also do authentication based on claims. But remember that Claims Authentication requires Policies, these Policies are created on the Application’s Configuration.

In my previous tutorial on Claims in Identity Membership System I explained how to create claims for a user. Now I will teach how to provide authentication based on claims for users. I will do this by making Policies.

Creating a Policy

I will create a policy on the ConfigureServices method of the Startup class, see the highlighted startup class’s code below:

using Identity.IdentityPolicy;
using Identity.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Identity
{
    public class Startup
    {
        public Startup(IConfiguration configuration) => Configuration = configuration;
        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
            services.AddTransient<IUserValidator<AppUser>, CustomUsernameEmailPolicy>();
            services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
            services.AddAuthorization(opts => {
                opts.AddPolicy("AspManager", policy => {
                    policy.RequireRole("Manager");
                    policy.RequireClaim("Coding-Skill", "ASP.NET Core MVC");
                });
            });
            services.AddIdentity<AppUser, IdentityRole>(opts =>
            {
                opts.User.RequireUniqueEmail = true;
                opts.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz";
                opts.Password.RequiredLength = 8;
                opts.Password.RequireNonAlphanumeric = true;
                opts.Password.RequireLowercase = false;
                opts.Password.RequireUppercase = true;
                opts.Password.RequireDigit = true;
            }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseStatusCodePages();
            app.UseDeveloperExceptionPage();
            app.UseStaticFiles();
            app.UseAuthentication();
            app.UseMvcWithDefaultRoute();
        }
    }
}

You can see I added a policy called ‘AspManager’:

services.AddAuthorization(opts => {
    opts.AddPolicy("AspManager", policy => {
        policy.RequireRole("Manager");
        policy.RequireClaim("Coding-Skill", "ASP.NET Core MVC");
    });
});

It has 2 requirements:

1. User should be in ‘Manager’ Role.
2. User should have the claim type as ‘Coding-Skill’ and its value should be ‘ASP.NET Core MVC’.

Now, go to the ‘Claims Controller’ and add a new action method called ‘Project’, with the Authorize attribute stating policy value to be ‘AspManager’:

[Authorize(Policy = "AspManager")]
public ViewResult Project() => View("Index",User?.Claims);

Now, this Project Action can only be invoked by Users who are in Manager Role, and they should have a claim ‘Coding-Skill’ with value ‘ASP.NET Core MVC.

Add Policy Methods

I used the AddPolicy method to create my policy. Inside the AddPolicy method I used the RequireRole and RequireClaim methods to create the policy requirements, see below code:

services.AddAuthorization(opts => {
    opts.AddPolicy("AspManager", policy => {
        policy.RequireRole("Manager");
        policy.RequireClaim("Coding-Skill", "ASP.NET Core MVC");
    });
});

Some important methods to create your policy requirements are:

Name Description
RequireUserName(name) Specifies the specific user is required.
RequireClaim(type, value) Specifies that the user has a claim of the specified type and with one of a range of values. The claim values can be expressed as comma-separated arguments or as an IEnumerable.
RequireRole(roles) Specifies that the user should be a member of a specified Role. Multiple roles can be specified as comma-separated arguments or as an IEnumerable.
AddRequirements(requirement) Specifies a custom requirement to the policy. I will cover it in the coming topic.

Custom Requirement to a Policy

Suppose I want to create a Policy that has a requirement to allow only the user ‘Tom’ to access an Action method.

To do this I will have to create:

1. A class that will implement the IAuthorizationRequirement Interface.
2. A custom authorization handler, which are subclasses of the AuthorizationHandler class that evaluate the requirement for a given request.

I create a class called ‘AllowUserPolicy.cs’ inside the ‘CustomPolicy’ folder (in fact, you can create this class in any folder of your choice).

Add the following code to this class:

using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Identity.IdentityPolicy
{
    public class AllowUserPolicy : IAuthorizationRequirement
    {
        public string[] AllowUsers { get; set; }

        public AllowUserPolicy(params string[] users)
        {
            AllowUsers = users;
        }
    }

    public class AllowUsersHandler : AuthorizationHandler<AllowUserPolicy>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AllowUserPolicy requirement)
        {
            if (requirement.AllowUsers.Any(user => user.Equals(context.User.Identity.Name, StringComparison.OrdinalIgnoreCase)))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
            return Task.CompletedTask;
        }
    }
}

The AllowUserPolicy class implements the IAuthorizationRequirement interface, and takes all the allowed users through the constructor parameter in string array type.

The AllowUsersHandler class is an authorization handler as it is inherts the AuthorizationHandler class. The ‘HandleRequirementAsync’ method is called when the authorization system needs to check access to a Action method.

The arguments to the method are an ‘AuthorizationHandlerContext’ object, and the AllowUserPolicy class that provides data of the allowed user names.

The members of the ‘AuthorizationHandlerContext’ class are:

Name Description
Succeed(requirement) his method is called if the request meets the requirement. The argument, to this method, is the ‘AllowUserPolicy’ object received by the method.
Fail() This method is called if the request fails to meet the requirement.
Resource This property returns an object that is used to authorize access to a resource.

So, in the ‘HandleRequirementAsync’ method, I check if the Current Logged in user does come in the allowed user names. In that case I called the Succeed method, else fail method is called.

Notice that I get the logged in user name by – context.User.Identity.Name.

Now I have to create a Policy that will send the Allowed User names to my AllowUserPolicy class.

I will use the AddRequirements(requirement) method to specify the Custom Requirements to my Policy.

In the below code of the ConfigureServices method, I added a Custom Requirement to a Policy called ‘NotTom’. In order to create the Custom Requirement I have used the AddRequirements(requirement) method, and passed to it a new object of the ‘BlockUserPolicy’ class.

I have also registered the authorization handler class with the service provider as an implementation of the IAuthorizationHandler interface.

These 2 additions are highlighted in the below code of the ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddTransient<IAuthorizationHandler, AllowUsersHandler>();
    services.AddAuthorization(opts => {
        opts.AddPolicy("AllowTom", policy => {
            policy.AddRequirements(new AllowUserPolicy("tom"));
        });
    });
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
    services.AddMvc();
}

Now, all I have to do is add a new action method and apply my policy to it.

So added a new action method called ‘TomFiles’ to my Claims Controller:

[Authorize(Policy = "AllowTom")]
public ViewResult TomFiles() => View("Index", User?.Claims);

This action method will only be invoked if the Logged in use is ‘Tom’. If any other user tries to invoke it then he will be redirected to the Access Denied page.

To test it log in with tom’s credentials and then go to the URL – ‘/Claims/TomFiles’. You will find the Action method gets invoked, as shown by the image below:

action method invoked only by user tom

Now log in with some other account, and go to the same URL, and this time you will be redirected to the Access Denied Page.

Validating Request against a Policy using the IAuthorizationService Interface

You have seen that I apply the Policy, with the Authorize attribute, on the Action method. But sometimes there comes a situation where you want to validate a request against a policy, in the Controller’s action itself, and without applying the [[Authorize(Policy = “SomePolicy”)]] attribute on the Action.

You can certainly do this thing by IAuthorizationService Interface.

Create a ‘AllowPrivatePolicy.cs’ class in the ‘IdentityPolicy’ folder with the following code:

using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Identity.IdentityPolicy
{
    public class AllowPrivatePolicy : IAuthorizationRequirement
    {
    }

    public class AllowPrivateHandler : AuthorizationHandler<AllowPrivatePolicy>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AllowPrivatePolicy requirement)
        {
            string[] allowedUsers = context.Resource as string[];

            if (allowedUsers.Any(user => user.Equals(context.User.Identity.Name, StringComparison.OrdinalIgnoreCase)))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
            return Task.CompletedTask;
        }
    }
}

Next, register the authorization handler class, and create a new policy called ‘PrivateAccess’ inside the ConfigureServices method, as shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddTransient<IAuthorizationHandler, AllowPrivateHandler>();
    services.AddAuthorization(opts => {
    opts.AddPolicy("PrivateAccess", policy => {
        policy.AddRequirements(new AllowPrivatePolicy());
    });
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
    services.AddMvc();
}

Notice I am not passing any argument to the ‘AllowPrivatePolicy’ class – policy.AddRequirements(new AllowPrivatePolicy());, instead I will pass the information from my controller.

Finally, go to the Claims controller and add a dependency of IAuthorizationService interface to its controller.

Then add an action called ‘PrivateAccess’ that validates the request, against my newly created ‘PrivateAccess’ policy, using the authService.AuthorizeAsync method.

The updated code is given below:

private UserManager<AppUser> userManager;
private IAuthorizationService authService;

public ClaimsController(UserManager<AppUser> userMgr, IAuthorizationService auth)
{
    userManager = userMgr;
    authService = auth;
}

public async Task<IActionResult> PrivateAccess(string title)
{
    string[] allowedUsers = { "tom","alice"};
    AuthorizationResult authorized = await authService.AuthorizeAsync(User, allowedUsers, "PrivateAccess");

    if (authorized.Succeeded)
        return View("Index", User?.Claims);
    else
        return new ChallengeResult();
}

This ‘PrivateAccess’ Action method can only be invoked when the logged in user is either ‘tom’ or ‘alice’.

Notice I am providing the values of the allowedUsers, to my AllowPrivateHandler class, in the second argument of the AuthorizeAsync method. Check this code:

AuthorizationResult authorized = await authService.AuthorizeAsync(User, allowedUsers, "PrivateAccess");

My AllowPrivateHandler class gets the ‘allowedUsers’ value from its AuthorizationHandlerContext class ‘Resource’ property. Check this code:

string[] allowedUsers = context.Resource as string[];

The ‘ChallengeResult’ class initialization forces the users, other than ‘tom’ and ‘alice’, to the login page.

You can now test it by login with user ‘alice’ credentials, and then going to the URL – ‘/Claims/PrivateAccess’. You will find the action method is invoked, as shown by the image below:

action invoked by users tom and alice only

Now log in with mary’s account and go to the same URL, only to find that you are now redirected to the login page.

This is a classic example of validating a request against a policy by sending validating requirements to the Authorization Handler.

You can download the full codes of this tutorial from the below link:

Download

Conclusion

This completes the tutorial on Policies in Identity. Now you can easily provide Claims based Authentication to Users by making Policies.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.