Search code examples
c#asp.net-coreasp.net-authorizationasp.net-core-5.0

How to get user input parameters in my authorization policy requirement in ASP.NET Core 5.0?


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>();

Solution

  • 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)
    {
        // ...
    }