Search code examples
c#asp.net-mvcasp.net-core-mvc-2.0

Custom AuthorizeAttribute throwing error after OnAuthorization


I want to do a custom AuthorizeAttribute. I want to use the IMemoryCache to store the tokens and i'm using a custom provider to inject the IMemoryCache instance. My problem is after OnAuthorization method it is not called my controller's action and it throws an internal server error that i'm not able to catch.

And here is the implementation so far

public class ApiAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public IMemoryCache Cache { get; set; }

    /// <summary>
    /// Verifica se o token é válido na sessão
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //Check we have a valid HttpContext
        if (context.HttpContext == null)
            throw new ArgumentNullException("httpContext");

        string token;

        token = context.HttpContext.Request.QueryString.Value;
        if (String.IsNullOrEmpty(token))
            token = context.HttpContext.Request.Form["token"];

        if (String.IsNullOrEmpty(token))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        if (Cache == null)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        if (token.Contains("="))
        {
            token = token.Split('=')[1];
        }

        var tokens = Cache.Get<Dictionary<string, User>>("tokens");
        var result = (from t in tokens where t.Key == token select t.Value).ToList();

        var controller = (string)context.RouteData.Values["controller"];
        var action = (string)context.RouteData.Values["action"];

        if (result.Count < 1)
            context.Result = new UnauthorizedResult();

    }

}



    public class CacheProvider : IApplicationModelProvider
{
    private IMemoryCache _cache;

    public CacheProvider(IMemoryCache cache)
    {
        _cache = cache;
    }
    public int Order { get { return -1000 + 10; } }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            // pass the depencency to controller attibutes
            controllerModel.Attributes
                .OfType<ApiAuthorizeAttribute>().ToList()
                .ForEach(a => a.Cache = _cache);

            // pass the dependency to action attributes
            controllerModel.Actions.SelectMany(a => a.Attributes)
                .OfType<ApiAuthorizeAttribute>().ToList()
                .ForEach(a => a.Cache = _cache);
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        // intentionally empty
    }
}

And here is the controller

    [ApiAuthorize]
    [HttpPost]
    public JsonResult Delete([FromForm] string inputId)
    {
        //Do stuff
    }

Thank in advance


Solution

  • After some digging i found this way to do this, i dont know if it is the best way to achieve that

    I Created a policy requirement

    public class TokenRequirement :  IAuthorizationRequirement
    {
    
    }
    

    And an AuthorizationHandler

        public class TokenRequirementHandler : AuthorizationHandler<TokenRequirement>
    {
        public IMemoryCache Cache { get; set; }
    
        public TokenRequirementHandler(IMemoryCache memoryCache)
        {
            Cache = memoryCache;
        }
    
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
        {
            return Task.Run(() => { //access the cache and then
            context.Succeed(requirement); });
        }
    }
    

    On my Startup i registered the handler and add the Authorization

            services.AddAuthorization(options =>
            {
                options.AddPolicy("Token",
                    policy => policy.Requirements.Add(new Authorize.TokenRequirement()));
            });
    
    
            services.AddSingleton<IAuthorizationHandler, Authorize.TokenRequirementHandler>();
    

    In the controller i used the Authorize attribute

        [Authorize(Policy = "Token")]
        [HttpPost]
        public JsonResult Delete([FromForm] string inputId)
        {
           //Do stuff
        }
    

    And now it works. Thank you.