2

I would like to require one policy for all actions on a controller, and I would like to also require a second policy for all calls to HTTP "edit methods" (POST, PUT, PATCH, and DELETE). That is, the edit methods should require both policies. Due to implementation requirements, and also a desire to keep the code DRY, I need the latter policy to be applied at the controller level, not duplicated on all the action methods.

As a simple example, I have a PeopleController, and I also have two permissions, implemented as Policies, ViewPeople and EditPeople. Right now I have:

[Authorize("ViewPeople")]
public class PeopleController : Controller { }

How do I go about adding the EditPeople policy/permission such that it "stacks" and only applies to the edit verbs?

I've run into two problems which both seem to be a real pain:

  • You can't have more than one AuthorizeAttribute or more than one Policy specified within the AuthorizeAttribute, AFAIK.
  • You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod to check it.

I tried working around the former with a custom Requirement and AuthorizationHandler, like so:

public class ViewEditRolesRequirement : IAuthorizationRequirement
{
    public ViewEditRolesRequirement(Roles[] editRoles, Roles[] viewRoles)
        => (EditRoles, ViewRoles) = (editRoles, viewRoles);

    public Roles[] EditRoles { get; }
    public Roles[] ViewRoles { get; }
}

public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
    {
        if (context.User != null)
        {
            var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
            var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
            if (context. // Wait, why can't I get to the bloody HttpRequest??
        }
        return Task.CompletedTask;
    }
}

... but I got as far as if (context. before I realized that I didn't have access to the request object.

Is my only choice to override the OnActionExecuting method in the controller and do my authorization there? I assume that's frowned upon, at the very least?

2 Answers 2

6

You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod...

Actually, we can access the Request in an AuthorizationHandler. We do that by casting the context.Resource with the as keyword. Here is an example:

services.AddAuthorization(config =>
{
    config.AddPolicy("View", p => p.RequireAssertion(context =>
    {
        var filterContext = context.Resource as AuthorizationFilterContext;
        var httpMethod = filterContext.HttpContext.Request.Method;
        // add conditional authorization here
        return true; 
    }));

    config.AddPolicy("Edit", p => p.RequireAssertion(context =>
    {
        var filterContext = context.Resource as AuthorizationFilterContext;
        var httpMethod = filterContext.HttpContext.Request.Method;
        // add conditional authorization here
        return true;
    }));
});

You can't have more than one AuthorizeAttribute....

Actually, we can have more than one AuthorizeAttribute. Note from the docs that the attribute has AllowMultiple=true. That allows us to "stack" them. Here is an example:

[Authorize(Policy="View")]
[Authorize(Policy="Edit")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    ...
}
Sign up to request clarification or add additional context in comments.

6 Comments

Excellent. As far as the issue with multiple goes, I'm certain the error message I saw said that AllowMultiple=false; I didn't bother checking the source on GitHub until now since the message was so clear. I'll have to figure out why I saw what I saw. Thanks.
So, that said, I realize this is a quick path to a solution, but seeking advice here.. is this the best way to accomplish what I'm trying to do? I feel like what I'm doing is really convoluted. Doing the same thing in MVC 6 was only 10 lines of code in a custom attribute. Advice/thoughts?
Off the top of my head, I don't know of a better way to accomplish what you're trying to do. All I can suggest is to give a deep read of the authorization documentation here: learn.microsoft.com/en-us/aspnet/core/security/authorization/…
@ShaunLuttin do you know if these policies can be applied to especific handler methods? I've been trying similar to what you've shown here, but haven't succeeded. Docs say for method restriction, use filter, but it is not adviceable, i believe, to code a filter per page, neither a monolithic BasePageModel for all filters.
@B.León I don't know off the top of my head.
|
2

You can have an IHttpContextAccessor injected into your handler and use it in HandleRequirementAsync:

public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{

    private readonly IHttpContextAccessor _contextAccessor;

    public ViewEditRolesHandler(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
    {
        if (context.User != null)
        {
            var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
            var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
            if (_contextAccessor.HttpContext.Request. // Now you have it!
        }
        return Task.CompletedTask;
    }
}   

1 Comment

Oh wow, excellent! I realize this is a quick path to a solution, but seeking advice here.. is this the best way to accomplish what I'm trying to do? I feel like what I'm doing is really convoluted. Doing the same thing in MVC 6 was only 10 lines of code in a custom attribute.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.