I asked similar question before and with others help I made some progress, but still not sure what I did wrong or missed here.
My application is kind of simple: Domain users get authenticated. Authenticated user(author) creates a request, save in database. Other authenticated users can view the request only. Author and admin users can edit/delete the request.
This is one of the example I followed: Different API functionality for different roles
And the other one PoliciesAuthApp: https://github.com/aspnet/Docs/tree/master/aspnetcore/security/authorization/policies/samples/PoliciesAuthApp1 I am not sure how the PermissionHandler is used/registered/invoked in here.
Here is my code:
Startup.cs
// Add Authentication
// Global filter
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Role - Domain Users")
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
// Add Authorization Handlers
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy => policy.Requirements.Add(new EditRequirement()));
});
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
EditRequirement.cs
public class EditRequirement : IAuthorizationRequirement
{
}
PermissionHandler.cs
public class PermissionHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is ReadRequirement)
{
if (IsOwner(context.User, context.Resource) ||
IsAdmin(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditRequirement ||
requirement is DeleteRequirement)
{
if (IsOwner(context.User, context.Resource) || IsAdmin(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
private bool IsAdmin(ClaimsPrincipal user, object resource)
{
if (user.IsInRole("Role - Administrator"))
{
return true;
}
return false;
}
private bool IsOwner(ClaimsPrincipal user, object resource)
{
if (resource is CreateRequestViewModel)
{
var ctx = (CreateRequestViewModel)resource;
if (ctx.RequestEnteredBy.Equals(user.Identity.Name))
{
return true;
}
}
else if (resource is AuthorizationFilterContext)
{
var afc = (AuthorizationFilterContext)resource;
// This is not right, but I don't know how to deal with AuthorizationFilterContext
// type passed into resource parameter when I click Edit button trying to edit the request
if (afc.HttpContext.User.Identity.Name.Equals(user.Identity.Name))
{
return true;
}
}
else if (resource is Request)
{
var r = (Request)resource;
if (r.RequestEnteredBy.Equals(user.Identity.Name))
{
return true;
}
}
return false;
}
private bool IsSponsor(ClaimsPrincipal user, object resource)
{
// Code omitted for brevity
return true;
}
}
RequestsController.cs
private IAuthorizationService _authorizationService;
public RequestsController(ApplicationModelContext context, IAuthorizationService authorizationService)
{
_context = context;
_authorizationService = authorizationService;
}
[Authorize(Policy = "EditPolicy")]
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
CreateRequestViewModel crvm = new CreateRequestViewModel();
var request = await _context.Request
.SingleOrDefaultAsync(m => m.RequestId == id);
if (request == null)
{
return NotFound();
}
var authorizationResult = await _authorizationService.AuthorizeAsync(User, request, "EditPolicy");
if (authorizationResult.Succeeded)
{
// Load request contents and return to the view
return View(crvm);
}
// This needs to be changed to redirect to a message screen saying no permission
return RedirectToAction("Details", new { id = request.RequestId });
}
When I was debugging the application, I found that:
- The first time when the page loads requests from database, it passes a CreateRequestViewModel type object to the resource parameter of IsOwner method in PermissionHandler.
- After click Edit button of the request item on page, it passes AuthorizationFilterContext type
- Then goes inside the Edit action in ReuqestController, which sends a Request type(in _authorizationService.AuthorizeAsync).
Not sure if I did something duplicated or completely mixed with different approaches.
Any advise would be really appreciated.
It looks like you are mixing policy and resource based authorization. Resource-based sounds like what you want, since you may not want to create policies for each CRUD operation, i.e "CreateUserPolicy", "UpdateUserPolicy", and then pass the different requirements to each one. See this tutorial for resource-based authorization: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.2
For authorizing against a user resource, I created a UserAuthorizationHandler:
Startup.cs:
UserAuthorizationHandler.cs
Controller: