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 :)