Override Global Authorize in Controller instead of Action - c#

I created a custom authorize, which is ignored when an action has [Authorize]:
public class MyGlobalAuthorizeAttribute: AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// check if action is decorated with an Authorize...
var action = filterContext.ActionDescriptor
if (action.IsDefined(typeof(AuthorizeAttribute), true))
return;
base.OnAuthorization(filterContext);
}
}
...then I configured it in the global filters, only allowing admins by default:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyGlobalAuthorizeAttribute() { Roles = "Admin" });
}
}
If I decorate an action like this:
public class MyController: Controller
{
[Authorize] // non-admins can access this action..
public ActionResult Index()
{
}
}
...it works fine. However, if I put the [Authorize] in controller, MyGlobalAuthorizeAttribute won't detect it.
I found many examples of overriding, but all of them is about an action overriding a controller or a global authorize, but not a controller overriding a global authorize.
Is it possible to achieve this?

You also need to check the ControllerDescriptor:
var action = filterContext.ActionDescriptor;
if (action.IsDefined(typeof(AuthorizeAttribute), true))
return;
if (action.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
return;
Documentation for ControllerDescriptor.IsDefined(...)

Related

Override Authorization Role For controller

I have a Default Role for all controllers configured in my global.asax
protected override void Configure(HttpConfiguration config)
{
//Note: Client Authentication Filter is just a fancy AuthorizeAttribute
config.Filters.Add(new ClientAuthenticationFilter(APIRoles.MYAPI));
}
This adds a requirement for a role to all controllers. I would like to override that role for a specific controller
public class MFAController : ApiController
{
[HttpGet]
[Route(AuthAPIRoutes.GET_MFA_DEVICES)]
[Authorize(Roles = "MyCustomRoles")]
public MFAMethodDTO[] GetMultiFactorMethods()
{
return GlobalFactory<IMFASecurityService>.Instance.GetMultiFactorMethods();
}
//...
}
However when I do this. I get an error because my role original role APIRoles.MYAPI is missing. Is there a default way to override the AuthorizationAtrributes for controllers so they take precedence over the Global Filter?
If I understand correctly, you can do like below. This is not exactly you want, it needs to customize.
public class MyAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
// ...
// if (HttpContext.Current.User == null || HttpContext.Current.User.Identity == null || !HttpContext.Current.User.Identity.IsAuthenticated)
// throw new Exception("Not logged in");
}
}
[MyAuthorize]
public bool DoSomthing()
{
...
}

Override redirecturl when not authorized .net core

Basicly what I want to accomplish is something like this:
[AllowAnonymous]
    public ActionResult MyAction(string id)
    {
        if (Request.IsAuthenticated)
        {
            //do stuff
return View();
        }
//if we come here, redirect to my custom action
return RedirectToAction("Action","Controller", new {#id=id});
    }
But instead of this approach I'm trying to find out if it's possible to do the same thing with a custom attribute, looking something like this:
[Authorize(RedirectIfNotAuthorized="~/Controller/Action/id")]
public ActionResult MyAction(string id)
{
//do stuff
return View();
}
From what I've read it is possible to change the default url ("/Account/Login") to another url by changing the configuration for Asp.net Identity. This is not what I want. I want in some specific cases to redirect to another url.
(Backstory to this is that in some cases when a user is logged out due to inactivity some redirect urls are not wanted. Lets say a user was about to download a file from a url like ("~/Files/Pdf/123") but gets logged out. I do not wish for the user to get redirected to this url when he logs back in).
You need custom attribute:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Attributes
{
public class AuthAttribute : ActionFilterAttribute
{
private readonly string _action;
private readonly string _controller;
private readonly string _role;
public AuthAttribute(string action, string controller, string role)
{
_action = action;
_controller = controller;
_role = role;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.IsInRole(_role))
{
filterContext.Result = new RedirectToRouteResult(new {action = _action, controller = _controller});
}
else
{
base.OnResultExecuting(filterContext);
}
}
}
}
Usage:
[Auth("Test", "Home", "Admin")]
public IActionResult Index()

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?
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
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.

Combine custom AuthorizeAttribute and RequireHttpsAttribute

I have a custom RequireHttpsAttribute and a custom AuthorizeAttribute that I apply in FilterConfig to ensure that all controllers uses HTTPS and authorizes in the same way.
I also have a controller action that need some other authorization. In that case I must first use [OverrideAuthorization] to override the global authorization filter, and then I can set the special authorization for this action.
But [OverrideAuthorization] will also override the CustomRequireHttpsAttribute since that also inherts from IAuthorizationFilter. What can I do so that I don't have to readd the CustomRequireHttpsAttribute attribute every time I override the authorization?
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomRequireHttpsAttribute());
filters.Add(new CustomAuthorizeAttribute(Role = "User"));
}
}
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideAuthorization]
[CustomRequireHttpsAttribute]
[CustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}
I ended up creating my own custom IFilterProvider based on a modified version of this post.
I added an extra attribute that I can use for those Controllers or Actions where I want to override the attribute that is set globally. It really does nothing else than extend CustomAuthorizeAttribute so that it carries the same functionality:
public class OverrideCustomAuthorizeAttribute : CustomAuthorizeAttribute {}
Then I create an IFilterProvider that checks the presence of any OverrideCustomAuthorizeAttributes in the list of filters. If so, remove all CustomAuthorizeAttributes from the list:
public class CustomFilterProvider : IFilterProvider
{
private readonly FilterProviderCollection _filterProviders;
public CustomFilterProvider(IList<IFilterProvider> filters)
{
_filterProviders = new FilterProviderCollection(filters);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = _filterProviders.GetFilters(controllerContext, actionDescriptor).ToArray();
var shouldOverrideCustomAuthorizeAttribute = filters.Any(filter => filter.Instance is OverrideCustomAuthorizeAttribute);
if (shouldOverrideCustomAuthorizeAttribute)
{
// There is an OverrideCustomAuthorizeFilterAttribute present, remove all CustomAuthorizeAttributes from the list of filters
return filters.Where(filter => filter.Instance.GetType() != typeof(CustomAuthorizeAttribute));
}
return filters;
}
}
I register this IFilterProvider in Global.asax.cs:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Some other stuff first....
var providers = FilterProviders.Providers.ToArray();
FilterProviders.Providers.Clear();
FilterProviders.Providers.Add(new CustomFilterProvider(providers));
}
}
And I register the global CustomAuthorizeAttribute in FilterConfig.cs just like before:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomRequireHttpsAttribute());
filters.Add(new CustomAuthorizeAttribute(Role = "User"));
}
}
The difference is that I use OverrideCustomAuthorizeAttribute in the controller instead:
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideCustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}
This way, CustomRequireHttpsAttribute is always set globally and never overridden.
According to this page, you can specify which filters to override in the OverrideAuthorizationAttribute.FiltersToOverride Property.
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideAuthorization(FiltersToOverride = typeof(CustomAuthorizeAttribute))]
[CustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}

Redirect From Action Filter Attribute

What is the best way to do a redirect in an ActionFilterAttribute. I have an ActionFilterAttribute called IsAuthenticatedAttributeFilter and that checked the value of a session variable. If the variable is false, I want the application to redirect to the login page. I would prefer to redirect using the route name SystemLogin however any redirect method at this point would be fine.
Set filterContext.Result
With the route name:
filterContext.Result = new RedirectToRouteResult("SystemLogin", routeValues);
You can also do something like:
filterContext.Result = new ViewResult
{
ViewName = SharedViews.SessionLost,
ViewData = filterContext.Controller.ViewData
};
If you want to use RedirectToAction:
You could make a public RedirectToAction method on your controller (preferably on its base controller) that simply calls the protected RedirectToAction from System.Web.Mvc.Controller. Adding this method allows for a public call to your RedirectToAction from the filter.
public new RedirectToRouteResult RedirectToAction(string action, string controller)
{
return base.RedirectToAction(action, controller);
}
Then your filter would look something like:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = (SomeControllerBase) filterContext.Controller;
filterContext.Result = controller.RedirectToAction("index", "home");
}
Alternatively to a redirect, if it is calling your own code, you could use this:
actionContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Error" })
);
actionContext.Result.ExecuteResult(actionContext.Controller.ControllerContext);
It is not a pure redirect but gives a similar result without unnecessary overhead.
I am using MVC4, I used following approach to redirect a custom html screen upon authorization breach.
Extend AuthorizeAttribute say CutomAuthorizer
override the OnAuthorization and HandleUnauthorizedRequest
Register the CustomAuthorizer in the RegisterGlobalFilters.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomAuthorizer());
}
upon identifying the unAuthorized access call HandleUnauthorizedRequestand redirect to the concerned controller action as shown below.
public class CustomAuthorizer : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = IsAuthorized(filterContext); // check authorization
base.OnAuthorization(filterContext);
if (!isAuthorized && !filterContext.ActionDescriptor.ActionName.Equals("Unauthorized", StringComparison.InvariantCultureIgnoreCase)
&& !filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.Equals("LogOn", StringComparison.InvariantCultureIgnoreCase))
{
HandleUnauthorizedRequest(filterContext);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result =
new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "LogOn" },
{ "action", "Unauthorized" }
});
}
}
It sounds like you want to re-implement, or possibly extend, AuthorizeAttribute. If so, you should make sure that you inherit that, and not ActionFilterAttribute, in order to let ASP.NET MVC do more of the work for you.
Also, you want to make sure that you authorize before you do any of the real work in the action method - otherwise, the only difference between logged in and not will be what page you see when the work is done.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Do whatever checking you need here
// If you want the base check as well (against users/roles) call
base.OnAuthorization(filterContext);
}
}
There is a good question with an answer with more details here on SO.
Try the following snippet, it should be pretty clear:
public class AuthorizeActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(FilterExecutingContext filterContext)
{
HttpSessionStateBase session = filterContext.HttpContext.Session;
Controller controller = filterContext.Controller as Controller;
if (controller != null)
{
if (session["Login"] == null)
{
filterContext.Cancel = true;
controller.HttpContext.Response.Redirect("./Login");
}
}
base.OnActionExecuting(filterContext);
}
}
Here is a solution that also takes in account if you are using Ajax requests.
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace YourNamespace{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustom : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
if (YourAuthorizationCheckGoesHere) {
string area = "";// leave empty if not using area's
string controller = "ControllerName";
string action = "ActionName";
var urlHelper = new UrlHelper(context.RequestContext);
if (context.HttpContext.Request.IsAjaxRequest()){ // Check if Ajax
if(area == string.Empty)
context.HttpContext.Response.Write($"<script>window.location.reload('{urlHelper.Content(System.IO.Path.Combine(controller, action))}');</script>");
else
context.HttpContext.Response.Write($"<script>window.location.reload('{urlHelper.Content(System.IO.Path.Combine(area, controller, action))}');</script>");
} else // Non Ajax Request
context.Result = new RedirectToRouteResult(new RouteValueDictionary( new{ area, controller, action }));
}
base.OnActionExecuting(context);
}
}
}
This works for me (asp.net core 2.1)
using JustRide.Web.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyProject.Web.Filters
{
public class IsAuthenticatedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
context.Result = new RedirectToActionResult(nameof(AccountController.Index), "Account", null);
}
}
}
[AllowAnonymous, IsAuthenticated]
public IActionResult Index()
{
return View();
}
you could inherit your controller then use it inside your action filter
inside your ActionFilterAttribute class:
if( filterContext.Controller is MyController )
if(filterContext.HttpContext.Session["login"] == null)
(filterContext.Controller as MyController).RedirectToAction("Login");
inside your base controller:
public class MyController : Controller
{
public void RedirectToAction(string actionName) {
base.RedirectToAction(actionName);
}
}
Cons. of this is to change all controllers to inherit from "MyController" class

Categories

Resources