4

I would like to do security validation in a custom attribute. A good example is if the user makes a GET request to retrieve an entity with a given id, I would like to intercept that request in the attribute, hand it off to an action filter, and then determine if the user has access to it. My only problem is how to retrieve the entity id. I can't pass it in the attribute declaration, because that gets initialized once instead of every request. Instead, I would like to give my custom attribute a url pattern like you would give HttpGet or HttpPost and have it resolve against the context's url parameters to result in an entity id.

Here is my attribute:

public class RequireProjectAccessAttribute : TypeFilterAttribute
{
    public string UrlPattern { get; set; }
    public RequireProjectAccessAttribute(string urlPattern) : base(typeof(RequireProjectAccessFilter))
    {
        UrlPattern = urlPattern;
        Arguments = new object[] { urlPattern };
    }

    private class RequireProjectAccessFilter : IAsyncActionFilter
    {
        private readonly ICurrentSession _currentSession;
        private readonly string _urlPattern;

        public RequireProjectAccessFilter(ICurrentSession currentSession, string urlPattern)
        {
            _currentSession = currentSession;
            _urlPattern = urlPattern;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var projectId = /* some magic to resolve _urlPattern against the current url parameter values */
            if (/* user doesn't have access to project */)
            {
                context.Result = new UnauthorizedResult();
            }
            else
            {
                await next();
            }
        }
    }
}

And here is how I would like to use it:

[Route("api/[controller]")]
public class ProjectsController : BaseController
{

    public ProjectsController()
    {
    }

    [RequireProjectAccess("{projectId}")]
    [HttpGet("{projectId}")]
    public JsonResult GetById(int projectId)
    {
        /* retrieve project */
    }
}
1
  • https :// channel9.msdn. com /Blogs/Seth-Juarez/Advanced-aspNET-Core-Authorization-with-Barry-Dorrans | Go to this video and skip to about 30 minutes. Commented Nov 22, 2016 at 19:40

1 Answer 1

3

You just need to provide the route key that should contain the value and then use the extension method GetRouteValue(string key) over the HttpContext object.

var projectId = context.HttpContext.GetRouteValue(_routeKey)?.ToString();

Which means your attribute would look like this:

public class RequireProjectAccessAttribute : TypeFilterAttribute
{
    public string RouteKey { get; set; }
    public RequireProjectAccessAttribute(string routeKey) : base(typeof(RequireProjectAccessFilter))
    {
        RouteKey = routeKey;
        Arguments = new object[] { routeKey };
    }

    private class RequireProjectAccessFilter : IAsyncActionFilter
    {
        private readonly ICurrentSession _currentSession;
        private readonly string _routeKey;

        public RequireProjectAccessFilter(ICurrentSession currentSession, string routeKey)
        {
            _currentSession = currentSession;
            _routeKey = routeKey;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var projectId = context.HttpContext.GetRouteValue(_routeKey)?.ToString();
            if (/* user doesn't have access to project */)
            {
                context.Result = new UnauthorizedResult();
            }
            else
            {
                await next();
            }
        }
    }
}

And used as this (notice I just pass the name of the route key):

[RequireProjectAccess("projectId")]
[HttpGet("{projectId}")]
public JsonResult GetById(int projectId)
{
    /* retrieve project */
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is close, but I want to resolve what gets passed into RequireProjectAccess in exactly the same way that HttpGet resolves its argument. I don't want to pass "projectId", I want to pass "{projectId}" and get MVC to use its internal route resolver to resolve it using the correct URL parameter.
I think I am missing something. Since "{projectId}" has already been resolved by the Routing and made the projectId parameter available in the RouteData, why do you need to resolve it again?

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.