Policy-Based Authorization in ASP.NET Core

Policy-Based Authorization in ASP.NET Core

Authorization in application software can ensure whether users can obtain resources, perform operations, or operate on resources. Asp. There are two methods in net core: Role based or Polocy based. The former is in ASP Net itself, which is ASP Net core.

Authorize attribute

Role is a text string whose value is treated by security layer as metadata (check whether it exists in IPrincipal object) and map a set of permissions to authenticated users in the program. Asp.Net is identified by an IPrincipal object in Asp.Net The real Identity in the Claim core. This class exposes a set of identities. Each Identity is identified by IIdentiy object, especially Claims Identity object, which means that any logged in user has a Claim list, which is actually a declaration of user status. Username and role are connected in ASP Two claims commonly used in net core However, the existence of role needs to store Identity data in the background. That is, users who log in through social authentication cannot see the role information.

Authorization goes further than authentication. Authentication is about discovering the Identity of users, and authorization defines the requirements for users to call the application interface. The user's Role information is usually stored in the database and retrieved when the user's credentials are verified. In this case, the Role information will be attached to the user account in some way. The iiidentity interface provides an Is In Role method that must be implemented. The Claims Identity class can be used to implement this method by checking whether the Role Claim is in the Claim collection generated by the authentication process. In any case, when a user tries to call a protected Controller method, her Role should be checked. If not, the user is denied access to any secured methods.

The Authorize attribute can be used to declare that the Controller or some of its methods are protected.

[Authorize]
public class CustomerController : Controller
{
...
}

If no parameter is specified, this attribute only checks whether the user is authenticated. More than that, this attribute also supports other attribute parameters, such as Roles. The Roles attribute means that users with any one of the listed Roles attribute values can access it. If multiple Roles are required, you can specify the Authorize attribute multiple times, or implement the filter filter yourself.

[Authorize(Roles="admin, system"]
public class BackofficeController : Controller
{
...
}

In addition, the Authorize property can accept one or more authentication schemes through the ActiveAuthenticationSchemes property.

[Authorize(Roles="admin, system", ActiveAuthenticationSchemes="Cookie"]
public class BackofficeController : Controller
{
...
}

The ActiveAuthenticationSchemes property is a well separated string that lists the Authentication middleware components trusted by the Authorization layer in the current context. As mentioned earlier, the string value passed to the ActiveAuthenticationSchemes property must match the Authentication middleware (Authentication Middleware) registered at application startup.

Here I would like to mention that in ASP Net 2.0 Authentication middleware is replaced by a service with multiple handlers. As a result, an Authentication Schema is a label for selecting a handler. More relevant Cookies, Claims and Authentication in ASP.NET Core"

Authorization Filter

The information provided by the Authorize attribute is used by the Authorization Filter provided by the system. Because it is responsible for detecting that users can perform certain operations, this filter will be displayed in ASP Net core before any other filter. If there is no authorized user, you can stop or cancel the request.

You can customize the Authorization filter, but the best way is to use the default filter and rely on the Authorization layer.

Roles, Permissions And Overrules

Roles does not satisfy all contemporary applications. For example, more permissions need to be subdivided under admin, which will lead to the problem of permission inheritance.

Roles are essentially horizontal concepts. To solve the above problems, although you can create different roles to implement User, Admin, CustomerAdmin and ContentsAdmin, when similar problems occur, you need to constantly increase roles. At this time, another authorization scheme based on Policy is required.

What is Policy

Asp.Net Core, the Policy based Authorization framework is designed to decouple Authorization and Application Logic In short, a Policy is an entity designed as a collection of requirements. These requirements themselves are the conditions that the current user must meet.

The simplest Policy is to authenticate the user, and the common requirement is that the user is associated with a given role. Another common requirement is that users have a specific Claim or a specific Claim with a specific value. In the most general terms, a requirement is an assertion about a user's identity that attempts to access a method that is true. Create a Policy object using the following code:

var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireClaim("editor", "contents") .RequireClaim("level", "senior")
.Build();

The builder object uses various extension methods to collect requirements, and then builds a Policy instance. As you can see, requirements act on any combination of authentication status and schemes, role s, and declarations read through authentication cookie s or bearer token s.

If the predefined extension methods for defining requirements are not suitable, you can define new requirements through your own assertions. The method is as follows:

var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireAssertion(ctx =>
{
return ctx.User.HasClaim("editor", "contents") ||
ctx.User.HasClaim("level", "senior");
})
.Build();

Please note that if you use RequireRole multiple times, the user must have all roles If you want to express an OR condition, you can use assertions.

Registering Policies

Defining policies is not enough. They must also be registered in the authorization middleware. To do this, you can add the authorization middleware as a service in the ConfigureServices method of Startup class, as shown below:

services.AddAuthorization(options=>
{
options.AddPolicy("ContentsEditor", policy =>
{
policy.AddAuthenticationSchemes("Cookie, Bearer");
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
policy.RequireClaim("editor", "contents");
});
}

Each Policy added to the middleware has a name, which is used to reference the Policy in the Authorize attribute of the controller class:

[Authorize(Policy = "ContentsEditor")]
public IActionResult Save(Article article)
{
// ...
}

Checking Policies Programmatically

public class AdminController : Controller
{
private IAuthorizationService _authorization;
public AdminController(IAuthorizationService authorizationService)
{
_authorization = authorizationService;
}

public async Task<IActionResult> Save(Article article)
{
   var allowed = await _authorization.AuthorizeAsync( User, "ContentsEditor"));
if (!allowed)
return new ForbiddenResult();
// Proceed with the method implementation 
...
}
}

Check of the policies from within a Razor view

@{ 
@inject IAuthorizationService Authorization
var authorized = await Authorization.AuthorizeAsync(
User, "ContentsEditor"))}
@if (!authorized)
{
<div class="alert alert-error">
You're not authorized to access this page.
</div>
}

Custom Requirements

The existing requirements basically include Claim and Authentication, and provide a general mechanism for customization based on assertion, but you can also customize the requirements at the same time. A Policy Requirement consists of two parts: a Requirement class that only saves data and an Authorization Handler for user Authentication data. Custom requirements expand the ability to handle specific policies. For example, expand the Content Editor Policy by adding requirements, so that users must have at least three years of working experience.

public class ExperienceRequirement : IAuthorizationRequirement
{
public int Years { get; private set; }

public ExperienceRequirement(int minimumYears)
{
Years = minimumYears;
}
}
public class ExperienceHandler : 
AuthorizationHandler<ExperienceRequirement>
{
protected override Task HandleRequirementAsync( 
AuthorizationHandlerContext context, 
ExperienceRequirement requirement)
{
// Save User object to access claims
var user = context.User; if (!user.HasClaim(c => c.Type == "EditorSince")) return Task.CompletedTask;

var since = user.FindFirst("EditorSince").Value.ToInt();
if (since >= requirement.Years)
context.Succeed(requirement);

return Task.CompletedTask;
}
}

The sample Authorization Handler reads the Claim associated with the user and checks the custom EditorSince Claim. If not found, the handler returns a failure.

A custom Claim should be a piece of information linked to the user in some way -- for example, a column in the user table -- and saved in the Authentication Cookie. Once you have a reference to a user, you can always find the user name in the declaration and run a query against any database or external service to gain years of experience and use the information in the handler.

The Authorization Handler calls the method succeeded and passes the current Requirement to notify that the Requirement has been successfully verified. If the Requirement fails, the Handler does not need to do anything but return. However, if the Handler wants to determine the failure of a Requirement without considering the fact that other handlers on the same Requirement may Succeed, it will call the Fail method on the Authorization Context object
.

Here's how to add custom requirements to a policy

services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast3Years",
policy => policy
.Requirements
.Add(new ExperienceRequirement(3)));
});

In addition, you must register a new Handler with the DI system within the scope of IAuthorization Handler:

services.AddSingleton<IAuthorizationHandler, ExperienceHandler>();

As mentioned earlier, a Requirement can have multiple handlers. When multiple handlers register the same Requirement for the Authorization layer in the DI system, at least one is successful.

Accessing the Current HTTP Context

In the implementation of Authorization Handler, you may need to check the request properties or routing data, as shown below:

if (context.Resource is AuthorizationFilterContext mvc)
{
var url = mvc.HttpContext.Request.GetDisplayUrl(); ...
}

Asp.Net Core, the Authorization Handler Context object has a Resource attribute of type filter context. The filter context varies according to the framework involved. For example, MVC and SignalR send their own specific objects. Whether type conversion is performed depends on what needs to be accessed. For example, user information always exists, so you don't need to cast it, but if you want MVC specific details, such as routing information, you must cast it.

Tags: C#

Posted by woodsonoversoul on Wed, 18 May 2022 15:37:40 +0300