I want to authorize users to see only their own resources (e.g: Audits
entity). So in the AuditController
I have:
[MyAuthorize(Policy = nameof(ValidUserToSeeAuditAuthorizationHandler))]
[HttpGet]
public async Task<JsonResult<AuditView>> GetByIdAsync(Guid id)
{
// my business to fetch the audit info based by its id
// ...
return result;
}
Then I created my Requirement
and AuthorizationHandler
classes:
public class ValidUserToSeeAuditRequirment : IAuthorizationRequirement
{
public ValidUserToSeeAuditRequirment(Guid auditId)
{
auditId = auditId;
}
public Guid AuditId { get; }
}
public class ValidUserToSeeAuditAuthorizationHandler : AuthorizationHandler<ValidUserToSeeAuditRequirment>
{
private readonly AppUserManager _userManager;
private readonly IUnitOfWork _appDbContext;
public ValidUserToSeeAuditAuthorizationHandler(AppUserManager userManager, IUnitOfWork appDbContext)
{
_userManager = userManager;
_appDbContext = appDbContext;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserToSeeAuditRequirment requirement)
{
if (!context.User.IsAuthenticated())
{
context.Fail();
return Task.CompletedTask;
}
var theAudit = _appDbContext.Set<Audit>().SingleOrDefault(x => x.Id == requirement.AuditId);
var authenticatedUserId = Convert.ToInt32(context.User.GetSubjectId());
// If the authenticated user created the audit, then he/she is valid to see it
if (theAudit.SubjectauthenticatedUserId == authenticatedUserId)
{
// valid
context.Succeed(requirement);
return Task.CompletedTask;
}
// he/she is not authorized to see the resource (audit)
context.Fail();
return Task.CompletedTask;
}
}
But in the Startup
class I want to configure authorization policies. How do I configure my Requirement
class to get the user input parameters from the controller's action method?
services.AddAuthorization(options =>
{
// another policies
// ...
options.AddPolicy(name: nameof(ValidUserToSeeAuditAuthorizationHandler),
policy =>
{
policy.RequireAuthenticatedUser();
policy.AddRequirements(new ValidUserToSeeAuditRequirment( /****** HERE, how to pass the controller action method parameters ******/));
});
});
services.AddTransient<IAuthorizationHandler, ValidUserToSeeAuditAuthorizationHandler>();
I ended up with this solution:
/// <summary>
///
/// </summary>
public class ValidUserToSeeAuditRequirment : IAuthorizationRequirement
{
}
/// <summary>
/// Only an Admin and the authorized user can see the Audit
/// </summary>
public class ValidUserToSeeAuditAuthorizationHandler : AuthorizationHandler<ValidUserToSeeAuditRequirment>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ValidUserToSeeAuditAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserToSeeAuditRequirment requirement)
{
// If he has the Admin Role, then he can see the Audit
if (context.User.HasClaim(x => x.Type.ToUpperInvariant() == "ROLE" && x.Value.ToUpperInvariant() == "ADMIN"))
{
context.Succeed(requirement);
return;
}
// Get the audit id from the Routing
var auditIdFromRoute = _httpContextAccessor.HttpContext.GetRouteData()?.Values["id"].ToString();
if (auditIdFromRoute is null || !Guid.TryParse(auditIdFromRoute, out Guid requestingAuditId))
{
context.Fail();
return;
}
// get the authenticated user
var userId = Convert.ToInt32(context.User.GetSubjectId());
// check if the user has authorized to see the audit
if(isUserAllowToSeeAudit(userId, requestingAuditId))
{
context.Succeed(requirement);
return;
}
context.Fail();
}
private bool isUserAllowToSeeAudit(int userId, Guid auditId)
{
// ...
}