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:
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:
services.AddScoped<IAuthorizationHandler, UserAuthorizationHandler>();
UserAuthorizationHandler.cs
public class UserAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, User>
{
private readonly IPermissionRepository _permissionRepository;
public UserAuthorizationHandler(IPermissionRepository permissionRepository)
{
_permissionRepository = permissionRepository;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, User resource)
{
var authUserId = int.Parse(context.User.FindFirstValue(ClaimTypes.NameIdentifier));
if (requirement == AuthorizationOperations.Create)
{
if (await CanCreate(authUserId))
{
context.Succeed(requirement);
}
}
else if (requirement == AuthorizationOperations.Read)
{
if (await CanRead(authUserId, resource))
{
context.Succeed(requirement);
}
}
}
/// <summary>
/// User can create if they have 'create' 'users' permission.
/// </summary>
/// <param name="authUserId">The requesting user</param>
/// <returns></returns>
public async Task<bool> CanCreate(int authUserId)
{
return await _permissionRepository.UserHasPermission(authUserId, "create", "users");
}
/// <summary>
/// User can read if reading themselves or they have the 'read' 'users' permission.
/// </summary>
/// <param name="authUserId">The requesting user</param>
/// <param name="user">The requested resource</param>
/// <returns></returns>
public async Task<bool> CanRead(int authUserId, User user)
{
return authUserId == user.Id || await _permissionRepository.UserHasPermission(authUserId, "read", "users");
}
}
Controller:
var authorized = await _authorizationService.AuthorizeAsync(User, new User { Id = id}, AuthorizationOperations.Read);
if (!authorized.Succeeded)
{
return Unauthorized();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With