Alex Zhukovskiy
Alex Zhukovskiy

Reputation: 10015

Add custom header for an Action in MVC

I googled and stackoverflowed this question a lot and its different variants, but I'm still confused if it's possible at all. I just want to add a custom header to all actions having specific attribute. Sounds simple? But it's not. I have just written following:

[AttributeUsage(AttributeTargets.Method)]
public class HelloWorldAttribute : ActionFilterAttribute
{
    /// <inheritdoc />
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Headers["X-HelloWorld"] = string.Empty;
    }
}

And it works fine for all requests except when they are forbidden by [Authorize] on Controller level.

I tried use to use this attribute for Controller level and pass names of methods that have to add this header to it, but it doesn't work too. It seems that Authorize has always a higher priority. And you can agree that it's ugly.

How can it be done?

Upvotes: 1

Views: 5501

Answers (2)

itikhomi
itikhomi

Reputation: 1570

You can write you own authorize attribute, and handle you code if note autorized or you can add Policy.

Oh i'm soo lazy...

here example

First add policy

public class ReplaceHeaderPolicy : IAuthorizationRequirement
{

}

public class ReplaceHeaderHandler : AuthorizationHandler<ReplaceHeaderPolicy>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReplaceHeaderPolicy requirement)
    {

        if (!context.User.Identity.IsAuthenticated)
        { 
            var fc = (FilterContext)context.Resource;
            fc.HttpContext.Response.Headers["X-HelloWorld"] = string.Empty;
        }

        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

then register your policy

services.AddAuthorization(options =>
        {
            options.AddPolicy("ReplaceHeader",
                              policy => policy.Requirements.Add(new ReplaceHeaderPolicy()));
        });

        services.AddSingleton<IAuthorizationHandler, ReplaceHeaderHandler>();

and use it on controller

[Authorize]
    [Authorize(Policy = "ReplaceHeader")]
    public IActionResult Index()
    {
        return View();
    }

Remove second [Authorize] to allow access for Unauthorized access

I hope it's helps

Upvotes: 2

Fran
Fran

Reputation: 6520

The problem is that the Authorize attribute is a guard against the controller method from actually running so your other attribute is never going to get run.

You can add a custom attribute like this

public class HandleUnauthorizedAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        base.OnException(filterContext);

        if (filterContext.Exception.GetType() != typeof(SecurityException)) return;

        var controllerName = (string)filterContext.RouteData.Values["controller"];
        var actionName = (string)filterContext.RouteData.Values["action"];
        var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

        filterContext.Result = new ViewResult
        {
            ViewName = "Unauthorized",
            ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
            TempData = filterContext.Controller.TempData
        };
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = 403;
        filterContext.HttpContext.Response.Headers.Add("X-HelloWorld", "");
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

which will handle the security exception that will get thrown from your Authorize attribute.

Or you could write a custom Authorize attribute that then adds you headers on response.

Upvotes: 2

Related Questions