TheMah
TheMah

Reputation: 459

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

Upvotes: 4

Views: 2931

Answers (4)

GrantA
GrantA

Reputation: 550

You shouldn't need to add any additional parameters to your IAuthorizationRequirement class or create an additional constructor in your AuthorizationHandler class.

What you're looking for is included as part of the AuthorizationHandlerContext context parameter of your handler.

In particular, you want the Resource property of that parameter. The value is going to be framework specific, but generally if you're using endpoint routing you'd do something like:

if (context.Resource is HttpContext httpContext)
{
   // Whatever you want to do with httpContext
}

With traditional routing or when using MVC's authorization filter, the value of Resource is an AuthorizationFilterContext instance, in which case you can do something like:

if (context.Resource is AuthorizationFilterContext authFilterContext)
{
   var httpContext = authFilterContext.HttpContext
   // Whatever you want to do with httpContext
}

Upvotes: 0

TheMah
TheMah

Reputation: 459

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

Upvotes: 2

GlennSills
GlennSills

Reputation: 4177

I would suggest you used the IAuthorizationRequirement and AuthorizationHandler approach. Instances of AuthorizationHandler (where T is the requirement) are registered as singletons in your startup. As such, you can inject an IHttpRequestAccessor into the handler, giving it the capability of accessing the request. This ends up being something like this

    public class YourAuthorizationHandler : AuthorizationHandler<YourAuthorizationRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    YourAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
   {
      _httpContextAccessor = httpContextAccessor;
   }

   protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, YourAuthorizationRequriement requirement)
   {
       var userIdInClaim = context.User.Claims.Where(claim => claimType == NameIdentifier).FirstOrDefault();
       var request = _httpContextAccessor.HttpContext.Request;
       request.EnableBuffering(); // allows the request to be read again

       // read the request from request.Body assuming an HTTP POST. It will depend.
       // do your logic checking the content here.
       return context.Succeed(requirement); // Assuming things are what you want.

   }
}

This is less complicated than it seems.

Read this Introduction to authorization in ASP.NET Core

Upvotes: 0

Karney.
Karney.

Reputation: 5031

You can customize a AuthorizationPolicy provider to get the parameter.

public class CustomAuthorizepolicyProvider: DefaultAuthorizationPolicyProvider
{
    public CustomAuthorizepolicyProvider(IOptions<AuthorizationOptions> options):base(options)
    {

    }
    public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        
        if (policyName=="[specified plicyname]")
            {
                var authorizePolicy = new AuthorizationPolicyBuilder();
                authorizePolicy.AddRequirements(new ValidUserToSeeAuditRequirment(/* give the parameter*/)).Build();
                return Task.FromResult(authorizePolicy);
            }
        return base.GetPolicyAsync(policyName);
    }
}

Inject into startup. Note: it is a singleton.

services.AddSingleton<IAuthorizationPolicyProvider,CustomAuthorizepolicyProvider>();

Upvotes: 1

Related Questions