Activity-based authorization in the ASP.NET core

We have an asp.net mvc application that I port to the aspnet mvc kernel. In the old solution, authentication is performed using Windows authentication.

In addition, we have “activity-based authentication” (for example, http://ryankirkman.com/2013/01/31/activity-based-authorization.html ); The user connects to the roles, and the roles are rights-related. User roles and associated rights are stored in a separate application, which serves as an authorization service for our application and several other systems.

A request to the api authorization service for "Jon Doe" user rights will receive a response like this:

{ Email:" Jon.Doe@acme.com ", FirstName:"Jon", LastName:"Doe", Resources: [ "CanAccessWebApplication", "CanCopyAppointment", "CanEditAppointment", "CanEditContact", "CanSaveContact" ... ] Alias:"1234567", UserId:"1234" } 

In our current application, these rights are checked using attributes (which we implemented ourselves) using the controller methods:

 public ContactController { [ActionUserAccess("CanSaveContact")] public ActionResult SaveContact { ... } } 

The current legacy implementation of the ActionUserAccessAttribute filter is as follows:

  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class ActionUserAccessAttribute : ActionFilterAttribute { private readonly string _accessRight; public ActionUserAccessAttribute(string accessRight) { _accessRight = accessRight; } public override void OnActionExecuting(ActionExecutingContext filterContext) { if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { throw new InvalidOperationException("ActionUserAccessAttribute can not be used for controllers or actions configured for anonymous access"); } base.OnActionExecuting(filterContext); var securityService = ContainerResolver.Container.GetInstance<ISecurityService>(); var hasResource = securityService.HasAccess(_accessRight); if (!hasResource) { filterContext.Result = new HttpStatusCodeResult( 403, string.Format( "User {0} is not authorized to access the resource:'{1}' ", filterContext.HttpContext.User.Identity.Name, _accessRight)); } } } } 

Porting an attribute / filter to aspnetcore seems pretty straightforward, but according to this answer, https://stackoverflow.com/a/212960/ ... we should not use asp.net security @blowdart.

If you don't port the custom filter to aspnetcore, what would be the best place to implement it here? Perhaps we could use role-based authentication https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles ? Could we create middleware that populates user access rights from api authorization service and equalizes rights and adds them as ClaimTypes.Role to ClaimsIdentity users? Then we will use the method above:

 [Authorize(Roles = "CanSaveContact")] public ActionResult Save() 

The inconsistency of this approach is that this is not about roles, but about access rights.

I also looked at policy-based authorization:

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies

What might look like this in the controller:

 [Authorize(Policy = "CanSaveContact")] public ActionResult Save() 

But when I read the code in the example based on Microsoft policies, I have to add all the available permissions that exist in the api security service, as the policies in the ConfigureService method of the Startup class in order to be able to use them. It seems uncomfortable to me (pseudocode):

 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); IEnumerable<string> allAccessRights = _securtiyService.GetAllAccessRights(); services.AddAuthorization(options => { foreach(var accessRight in allAccessRights) { options.AddPolicy(accessRight, policy => policy.Requirements.Add(new AccessRightRequirement(accessRight)); } }); services.AddSingleton<IAuthorizationHandler, AccessRightHandler>(); } 

After that, AccessRightHandler will correctly check the access right for the user. The writing of AccessRightHandler is fine, but there seems to be no need to add all rights as policies.

What would be the best approach to implement such authorization in our aspnetcore application?

+5
source share
2 answers

Great question, and I think that a few people will have the same problem as for ASP.NET Core.

Barry Dorrance (@blowdart) is absolutely right, you should not write your own authorization attributes. The authorization in ASP.NET Core is greatly improved, and you can definitely configure it for your needs.

Of course, this will greatly depend on your current application and what roles you have, so I will make some assumptions based on the above snippets.

Before you begin, I REALLY recommend that you read the new Authorization Documents for the ASP.NET core, as well as the Barry Dorran Authorization Workshop on GitHub. I highly recommend you go through the latter, and there is a .NET Core 2.0 branch there.

Depending on how you want to implement it, you can either go with authorization based on requirements, or go to a resource.

Looking at your roles, it seems that resource-based auth can really work just fine in your case!

For instance:

Identify the possible operations (the Name operation should be selected from your resources):

 public static class Operations { public static OperationAuthorizationRequirement Access = new OperationAuthorizationRequirement { Name = "Access" }; public static OperationAuthorizationRequirement Copy = new OperationAuthorizationRequirement { Name = "Copy" }; public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" }; public static OperationAuthorizationRequirement Save = new OperationAuthorizationRequirement { Name = "Save" }; public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" }; } 

Create an authorization handler for the base resource:

 public abstract class BaseResourceAuthorizationHandler<TResource> : AuthorizationHandler<OperationAuthorizationRequirement, TResource> { private readonly string _resourceType; public BaseResourceAuthorizationHandler(string resourceType) { _resourceType = resourceType; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, TResource resource) { if (context.User.HasClaim("Resources", $"Can{requirement.Name}{_resourceType}")) { context.Succeed(requirement); } return Task.CompletedTask; } } 

Implementation of certain resource-based handlers. Resources bind objects in your application to objects in your resources. This class will be the glue between your current resource roles, operations and the authorization system in ASP.NET Core. They can also be expanded to add additional logic for any types / operations of resources.

For example, for meetings:

 public class AppointmentAuthorizationHandler : BaseResourceAuthorizationHandler<Appointment> { public AppointmentAuthorizationHandler() : base("Appointment") { } } 

What do you then register:

 services.AddSingleton<IAuthorizationHandler, AppointmentAuthorizationHandler>(); 

Then in your controllers:

 public class AppointmentsController : Controller { IAppointmentsRepository _appointmentsRepository; IAuthorizationService _authorizationService; public AppointmentsController(IAppointmentsRepository appointmentsRepository, IAuthorizationService authorizationService) { _appointmentsRepository = appointmentsRepository; _authorizationService = authorizationService; } public IActionResult Edit(int id) { var appointment = _appointmentsRepository.Get(id); if (appointment == null) { return new NotFoundResult(); } if (!(await _authorizationService.AuthorizeAsync(User, appointment, Operations.Edit))) { return new ChallengeResult(); } return View(appointment); } } 

You can also do the same in the views to check if the user is allowed to see the "Edit" button, for example:

 @using Microsoft.AspNetCore.Authorization @model IEnumerable<Appointment> @inject IAuthorizationService AuthorizationService <h1>Document Library</h1> @foreach (var appointment in Model) { if (await AuthorizationService.AuthorizeAsync(User, appointment, Operations.Edit)) { <p>@Html.ActionLink("Appointment #" + appointment.Id, "Edit", new { id = appointment.Id })</p> } } 

PS Just add a note - yes, you lose the ability to filter by attribute, but in the end it's better. First of all, you move away from String-based roles, you request permissions based on the type of operation and the type of resource. Secondly, you can handle permissions much better (and reasonably), as well as combine multiple permission checks.

It looks more complex, but it is also MUCH more powerful :)

+3
source

Come here to play the devil's lawyer and offer an alternative to my other answer - this might be a simpler option based on @mortb's request, and might fit in some people who migrate from their current systems.

Based on your situation, a policy-based auth doesn’t really place your usecase - it’s a more powerful option, you don’t actually use it except to check if there is a Resource string from your API.

On the other hand, I would not give up Raleigh's approach. The list of resources that you get from the external API is not strictly resources, but at the same time it perfectly displays your needs. At the end of the day, all you are trying to do is check if the user has one (or several) resource permissions for a particular request.

As you mentioned in your post, you will need to expand your permission to populate roles from an external API. Remember that your ClaimsIdentity has a RoleClaimType property that indicates the type of RoleClaimType used to store the roles. It is usually installed on ClaimTypes.Role , but not always.

You can even go and create custom auth attributes, unlike this:

 public class AuthorizeAccessAttribute : AuthorizeAttribute { public AuthorizeAccessAttribute(string entity) { Roles = "CanAccess" + entity; } } public class AuthorizeEditAttribute : AuthorizeAttribute { public AuthorizeEditAttribute(string entity) { Roles = "CanEdit" + entity; } } 

So you can use it as follows:

 [AuthorizeEdit("Appointment")] public IActionResult Edit(int id) { return View(); } 
+2
source

Source: https://habr.com/ru/post/1271965/


All Articles