Search code examples
c#asp.net-coreauthorizationasp.net-core-mvcactionresult

Custom authorization IActionResults in aspnet-5 mvc-6


In ASP.NET 4 MVC5, I had this class that allowed me to return custom responses for unauthenticated responses to JSON endpoints. Here it is.

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (IsAjax(filterContext))
        {
            filterContext.Result = new JsonResult
            {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new
                {
                    success = false,
                    error = "You must be signed in."
                }
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }

    private bool IsAjax(AuthorizationContext filterContext)
    {
        return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
                null;
    }
}

However, in MVC6, the new AuthorizeAttribute is no overrides for creating custom IActionResult results. How do I do this in MVC6?


Solution

  • I finally figured it out after looking at the source.

    public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        Func<CookieRedirectContext, Task> _old;
        public CustomCookieAuthenticationEvents()
        {
            _old = OnRedirectToLogin;
            OnRedirectToLogin = OnCustomRedirectToLogin;
        }
    
        public Task OnCustomRedirectToLogin(CookieRedirectContext context)
        {
            var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
            if (actionContext.ActionContext == null)
                return _old(context);
    
            if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
            {
                // this is an ajax request, return custom JSON telling user that they must be authenticated.
                var serializerSettings = context
                    .HttpContext
                    .RequestServices
                    .GetRequiredService<IOptions<MvcJsonOptions>>()
                    .Value
                    .SerializerSettings;
    
                context.Response.ContentType = "application/json";
    
                using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
                {
                    using (var jsonWriter = new JsonTextWriter(writer))
                    {
                        jsonWriter.CloseOutput = false;
                        var jsonSerializer = JsonSerializer.Create(serializerSettings);
                        jsonSerializer.Serialize(jsonWriter, new
                        {
                            success = false,
                            error = "You must be signed in."
                        });
                    }
                }
    
                return Task.FromResult(0);
            }
            else
            {
                // this is a normal request to an endpoint that is secured.
                // do what ASP.NET used to do.
                return _old(context);
            }
        }
    }
    

    Then, use this event class as follows:

    services.Configure<IdentityOptions>(options =>
    {
        options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
    });
    

    ASP.NET 5 sure made simple things harder to do. Granted though, I can now customize things at a more granular level without effecting other pieces. Also, the source code is amazingly easy to read/understand. I am pretty happy having the confidence that any issue I am having can easily be identified as a bug or resolved by looking at the source.

    Cheers to the future!