Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading ASP.Net Core authorization policy from database

In ASP.Net Core we defined authorization policy in ConfigureServices method as below.

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

   services.AddAuthorization(options =>
   {
        options.AddPolicy("Founders", policy =>
           policy.RequireClaim ("EmployeeNumber", "1", "2", "3", "4", "5"));
   }
 }

and we use it on controller action as below.

[Authorize("Founders")]
public IActionResult GenerateReport()
{
   return View();
}

This is all fine and works perfectly. Now my question is, instead of defining "Founders" policy in code (using AddPolicy method in above snippet) how can I add policy details (name,claimtype, value) from database? Is there any hook provided in framework which I can use to populate policies from database?

Idea here is I should be able to add new Employee numbers to list (in database) and policy should be evaluated against that list. Obliviously I can pull this list from database in ConfigureServices itself however any new addition to employee list will not be picked up until application is restarted. Any pointers around this highly appreciated.

like image 507
Pankaj Kapare Avatar asked Feb 02 '17 01:02

Pankaj Kapare


People also ask

How would you apply an authorization policy to a controller in an ASP.NET Core application?

Role-Based Authorization in ASP.NET Core You can specify what roles are authorized to access a specific resource by using the [Authorize] attribute. You can even declare them in such a way that the authorization evaluates at the controller level, action level, or even at a global level. Let's take Slack as an example.

How do I add an authorization policy?

Authorization Policy The user must satisfy all the requirements. We Add the policy using the AddAuthorization method in the ConfigureServices of the startup class. options. AddPolicy("AdminOnly", policy => policy.

How do I Authorize my NET Core?

Authorization in ASP.NET Core is controlled with AuthorizeAttribute and its various parameters. In its most basic form, applying the [Authorize] attribute to a controller, action, or Razor Page, limits access to that component to authenticated users. Now only authenticated users can access the Logout function.


1 Answers

You need to combine Requirement and Authorization handlers, but not to populate policies from database, but for doing whatever authentication mechanism that you want to do.

First create a requirement class to feed to to the policy. Let's name it ThePolicyRequirement for now, more on this class later. second, create ThePolicyAuthorizationHandler and add it as a scoped service

services.AddAuthorization(options =>
{
    options.AddPolicy("ThePolicy", policy => policy.Requirements.Add( new ThePolicyRequirement() ));
});

services.AddScoped<IAuthorizationHandler, ThePolicyAuthorizationHandler>();

The key is that we can inject pretty much anything in ThePolicyAuthorizationHandler, and then we pass those injected objects and any other objects that are available at hand to ThePolicyRequirement, for it to determine wether the user is authenticated or not.

For example, here's my ThePolicyAuthorizationHandler for asp.net core 2 and 3:

public class ThePolicyAuthorizationHandler : AuthorizationHandler<ThePolicyRequirement>
{
    readonly AppDbContext _context;
    readonly IHttpContextAccessor _contextAccessor;

    public ThePolicyAuthorizationHandler(DbContext c, IHttpContextAccessor ca)
    {
        _context = c;
        _contextAccessor = ca;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ThePolicyRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext filterContext)
        {
            var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
            var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
            var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
            var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
            if (await requirement.Pass(_context, _contextAccessor, area, controller, action, id))
            {
                context.Succeed(requirement);                 
            }

        }
        else if (context.Resource is PolicyResource policyResource)
        {
            var pr = policyResource;
            if (await requirement.Pass(_context, _contextAccessor, pr.Area, pr.Controller, pr.Action, pr.Id))
            {
                context.Succeed(requirement);                    
            }
        }               
    }
}

UPDATE
apparently, things have changes a bit in .net 5, below ThePolicyAuthorizationHandler for .net 5:

public class ThePolicyAuthorizationHandler : AuthorizationHandler<ThePolicyRequirement>
{
    readonly AppDbContext _context;
    readonly IHttpContextAccessor _contextAccessor;

    public ThePolicyAuthorizationHandler(DbContext c, IHttpContextAccessor ca)
    {
        _context = c;
        _contextAccessor = ca;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ThePolicyRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext filterContext)
        {
            var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
            var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
            var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
            var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
            if (await req.Pass(_context, _contextAccessor, area, controller, action, id))
            {
                context.Succeed(req);
            }
        }
        if (context.Resource is DefaultHttpContext httpContext)
        {
            var area = httpContext.Request.RouteValues["area"].ToString();
            var controller = httpContext.Request.RouteValues["controller"].ToString();
            var action = httpContext.Request.RouteValues["action"].ToString();
            var id = httpContext.Request.RouteValues["id"].ToString();
            if (await req.Pass(_context, _contextAccessor, area, controller, action, id))
            {
                context.Succeed(req);
            }
        }               
    }
}

and my Requirement class:

public class ThePolicyRequirement : IAuthorizationRequirement
{
    AppDbContext _context;
    IHttpContextAccessor _contextAccessor;

    public async Task<bool> Pass(AppDbContext context, IHttpContextAccessor contextAccessor, string area, string controller, string action, string id)
    {
        _context = context;
        _contextAccessor = contextAccessor;
        
        //authorization logic goes here

        return await Task.FromResult(false);
    }
}

in my example, I pass my AppDbContext and HttpContextAssessor to ThePolicyRequirement, but refrain from passing the AuthorizationHandlerContext because in my case, i just need the area/controller/action name only. The important thing is we can pass almost any information available in our whole application to it.

Hope this will help.

like image 136
Riza Avatar answered Oct 03 '22 02:10

Riza