How to work with Policies in ASP.NET Core Identity

How to work with Policies in ASP.NET Core Identity

Just like Authenticating users based on Identity Roles, in the same way, you can also do Claims based authentication. But remember that Claims Authentication requires Policies, these Policies are created on the Startup.cs file.

Creating a Policy

I will create a policy on the ConfigureServices() method of the Startup.cs class, see the highlighted startup class code shown below:

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

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.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

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

            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //...
        }
    }
}

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");
    });
});

The policy I created has 2 requirements:

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

Now, go to the Claims Controller and add a new action method called Project. Put [Authorize(Policy = "AspManager")] attribute on it which states that the policy value should be AspManager for initiating this action method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Identity.Models;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;

namespace Identity.Controllers
{
    [Authorize]
    public class ClaimsController : Controller
    {
        private UserManager<AppUser> userManager;

        public ClaimsController(UserManager<AppUser> userMgr)
        {
            userManager = userMgr;
        }

        //...

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

Now, the Project action method can only be invoked by Users who are in Manager Role and have a Claim called Coding-Skill which has value of ASP.NET Core MVC.

Add Policy Methods

I used the AddPolicy method to create my Policy. Inside this method I used the RequireRole and RequireClaim methods to create the policy requirements as shown below:

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

Some important methods to create a Policy are given below.

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.
Testing Claims & Policy based Authentication

I will test this feature with a user called pintu. First I log in with pintu’s credentials by running the application and entering his credentials which are:

Email – [email protected], Password – [email protected]

Identity User Pintu

Although this Identity User belongs to the Role called Manager but does not have the Claim called Coding-Skill. You can check this by going to the url – https://localhost:44395/Claims once you did the login. Check the below image which shows the Claims of this user.

identity user pintu claims

Now I try accessing the url of the Project action of the Claims controller having the url – https://localhost:44395/Claims/Project. I simply fail to access it and get Access Denied message.

access denied

So I need to add the Claim called Coding-Skill with value ASP.NET Core MVC for the user pintu. This can done from the Create Claim Page whose url is – https://localhost:44395/Claims/Create (see below image).

Adding Claims Identity

After adding the claim I log-out from pintu’s account and re-login once again. This is a necessary step to see the added claims.

Next going to the claims page (url – https://localhost:44395/Claims) where I see the recently added claim. See the below image.

Claim added Pintu account

Finally accessing the url of the Project action of the Claims controller which is https://localhost:44395/Claims/Project. I am able to access it this time, and it shown me all the Claims associated with user Pintu’s account. Check below image.

All Claims of User
In the tutorial called How to Create, Read, Update & Delete users in ASP.NET Core Identity I created the functionality for adding, reading, updating and deleting users in Identity. You will find it very useful in this tutorial to.

Custom Requirement to a Policy

Suppose I want to create a Policy to allow only the user called 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 is a sub-class of the AuthorizationHandler class and whose work is to evaluate the requirement for a given request.

I create a class called AllowUserPolicy.cs inside a folder called CustomPolicy (note that you can create this class in any folder of your choice).

The AllowUserPolicy.cs class code is given below.

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

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

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

The AllowUserPolicy class implements the IAuthorizationRequirement interface and gets all the allowed users through the parameter of it’s constructor.

Next create another class called AllowUsersHandler inside the CustomPolicy folder with the code given below.

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

namespace Identity.CustomPolicy
{
    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 AllowUsersHandler class is an authorization handler as it inherits the AuthorizationHandler class. The HandleRequirementAsync() method is called when the authorization system needs to check access to an Action method.

The arguments to the method are an AuthorizationHandlerContext object and the AllowUserPolicy class that provides allowed user names.

The members of the AuthorizationHandlerContext class are:

Name Description
Succeed(requirement) This 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 comes 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’s 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 given code of the ConfigureServices() method of the Startup.cs class, I did 2 things which are:

  • 1. 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.
  • 2. I have also registered the authorization handler class with the service provider as an implementation of the IAuthorizationHandler interface.

These 2 things are highlighted in the below code of the ConfigureServices() method:

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

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

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
            services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

            services.AddTransient<IAuthorizationHandler, AllowUsersHandler>();
            services.AddAuthorization(opts => {
                opts.AddPolicy("AllowTom", policy => {
                    policy.AddRequirements(new AllowUserPolicy("tom"));
                });
            });

            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //...
        }
    }
}

Now, all I have to do is to apply my policy to an action method of a controller. So added a new action method called TomFiles to the Claims Controller and apply this policy to it. See the below code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Identity.Models;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;

namespace Identity.Controllers
{
    [Authorize]
    public class ClaimsController : 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 – https://localhost:44395/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 fail to invoke this action method and will be redirected to the Access Denied Page.

Validating Request against a Policy from IAuthorizationService Interface

You have seen that I applied 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.

You can certainly do this thing by taking the help of IAuthorizationService Interface.

Let us understand it with an example. So create a new class called AllowPrivatePolicy.cs inside the IdentityPolicy folder with the following code:

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

namespace Identity.CustomPolicy
{
    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 of the Startup.cs as shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

    services.AddTransient<IAuthorizationHandler, AllowPrivateHandler>();
    services.AddAuthorization(opts =>
    {
        opts.AddPolicy("PrivateAccess", policy =>
        {
            policy.AddRequirements(new AllowPrivatePolicy());
        });
    });

    services.AddControllersWithViews();
}

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 it’s constructor.

Then add an action method called PrivateAccess that validates the request against the newly created PrivateAccess policy. It does this by using the authService.AuthorizeAsync method.

The updated code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Identity.Models;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;

namespace Identity.Controllers
{
    [Authorize]
    public class ClaimsController : Controller
    {
        private UserManager<AppUser> userManager;
        private IAuthorizationService authService;

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

        //... removed for brevity

        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.

URL Routing is an exceptionally important topic so cover it fully from Learn ASP.NET Core Convention-Based URL Routing

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

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

The AllowPrivateHandler class gets the allowedUsers value from it’s AuthorizationHandlerContext class’s Resource property. Check this code:

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

The ChallengeResult 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 – https://localhost:44395/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.