I have a controller decorated with an AuthorizeAttribute. The controller contains several actions that all require authentication apart from one action that requires some custom authentication provided by CustomAuthorizeAttribute.
My question is once I've added [Authorize] at the controller level can I override it (or remove it) with [CustomAuthorize] on just one action? Or do I have to remove [Authorize] from the controller level and add it individually to every other action?
I'm asking purely for convenience because I'm lazy and don't want to decorate every action with the AuthorizeAttribute.
[Authorize]
public class MyController : Controller {
//requires authentication
public ViewResult Admin() {
return View();
}
//... a lot more actions requiring authentication
//requires custom authentication
[CustomAuthorize] //never invoked as already failed at controller level
public ViewResult Home() {
return View();
}
}
In MVC 5 you can override the authorization for any action using the new attribute OverrideAuthorization. Basically, you add it to an action that has a different authorization configuration than the one defined in the controller.
You do it like this:
[OverrideAuthorization]
[Authorize(Roles = "Employee")]
public ActionResult List() { ... }
More information at http://www.c-sharpcorner.com/UploadFile/ff2f08/filter-overrides-in-Asp-Net-mvc-5/
In ASP.NET Core 2.1 there's no OverrideAuthorization attribute and the only thing you can do is make an action anonymous, even if the controller is not. More information at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
One option is to do it this way:
[Authorize(Roles = "Admin,Employee")] // admin or employee
public class XController : Controller
{
[Authorize(Roles = "Admin")] // only admin
public ActionResult ActionX() { ... }
[AllowAnonymous] // anyone
public ActionResult ActionX() { ... }
}
You can change the Order in which the attributes run (using the Order property), but I believe that in this case they will still both run unless one generates a result with immediate effect. The key is to have the least restrictive attribute applied at the highest level (class) and get more restrictive for the methods. If you wanted the Home action to be publicly available, for instance, you would need to remove the Authorize attribute from the class, and apply it to each of the other methods.
If the action has the same level of permissiveness, but has a different result, changing the order may be sufficient. For example, you would normally redirect to the Logon action, but for Home you want to redirect to the About action. In this, case give the class attribute Order=2 and the Home action attribute Order=1.
After way too much time, I came up with a solution. You need to decorate your controller with a custom AuthorizeAttribute.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with AllowAnonymous on an Action
[AllowAnonymous]
All you need to override the [Authorize] from the controller, for a specific action is to add
[AllowAnonymous]
to the action you want to not be authorized (then add your custom attribute as required).
See the comments / intellisense :
Represents an attribute that marks controllers and actions to skip the
System.Web.Mvc.AuthorizeAttribute during authorization.
Full Example
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Website
{
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (true)//Perform own authorization logic
return; //simply return if request is authorized
context.Result = new UnauthorizedResult();
return; //this is not authorized
}
}
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]//When this is added our Custom Attribute is hit, without it our attribute is not used as request already gets 401 from controller's Authorize
[CustomAuthorize]
public IActionResult Index()
{
return View(new ViewModel());
}
}
Note
This approach will not work if you want to use the standard [Authorize] attribute on your action, with a custom policy e.g.
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]
[Authorize("CustomPolicyName")] //Will not be run
public IActionResult Index()
{
return View(new ViewModel());
}
}
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
false //Custom logic here
));
});
...but if like the OP you want a Custom Attribute then you are good to go with my solution.
Override for all controllers when handling prototype and production environment.
So there is no need to remove the authorize of each controller.
app.UseEndpoints(endpoint =>
{
endpoint.MapControllers().WithMetadata(new AllowAnonymousAttribute());
});
Related
If I register a global Authorize attribute in FilterConfig.cs so that each action method can only be accessible to authenticated users, and decorate some controllers with [Authorize(Role="Admin")] so that only admin users can access them, does authorization logic run twice on these controllers? What can I do to prevent that?
You can use an ASP.NET MVC "FilterProvider" provider. What this will do is help you to fetch all relevant filters from a specific controller and action.
So you can define your own provider and register that one instead of the default ones. this will give you full control over the asp.net filters and you can remove some filters based on your requirement.
Lets say we have following Controller.
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Whatever()
{
return View();
}
}
I think you are looking a way to do something as follows. concentrate on Index Action
[Authorize]
public class HomeController : Controller
{
[ExcludeFilter(typeof(AuthorizeAttribute))] // Excluding Authorize Important !
public ActionResult Index()
{
return View();
}
public ActionResult Admin() // will follow the declared authorize Attribute
{
return View();
}
}
If thats what you are Looking for then see this Article
I have a controller like this:
[Authorize(Users="Admin")]
public class MyController : Controller
{
...
[AllowAnonymous]
public AllUsersAction()
{
}
}
Except I actually do want to authorize AllUsersAction, only all authorized users should be able to hit it, not just Admin.
What to do?
EDIT: I know that I can authorize the whole controller and provide more restrictions for all actions that should only be available to Admin. But I'd rather not put attributes on every action but one.
The question could be better phrased: What would an implementation look like that would allow this 'minimalism' if it isn't currently possible?
Use Authorize attribute without any parameters for controller:
[Authorize]
public class MyController : Controller
{
...
public AllUsersAction()
{
}
[Authorize(Users="Admin")]
public ActionResult OnlyForAdmin()
{
}
}
And specify Authorize attribute Roles/Users properties for restricted actions.
Unfortunately Authorize attribute on controller bypasses authorization only if action has AllowAnonymous attribute. Fortunately you can override OnAuthorization method of Authorize attribute to skip authorization check in controller Authorize attribute if action has its own Authorize attribute:
public class CustomAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext.ActionDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
{
//skip authorization check if action has Authorize attribute
return;
}
base.OnAuthorization(filterContext);
}
}
You can use this CustomAuthorize in your example:
[CustomAuthorize(Users="Admin")]
public class MyController : Controller
{
...
[Authorize]
public AllUsersAction()
{
}
}
If I have the Authorize attribute on both the controller and the action, which one will take the effect? Or will both take effect?
You asked:
If I have Authorize attribute on both controller and action, which one will take the effect? Both?
To answer this simply: Both. The effect is to AND the two restrictions together. I'll explain why below ...
Details
So, there are a few reasons you could be asking this.
You want to know how to enforce an additional constraint on an Action compared to a method. e.g.
At controller level, enforce users in the role "user"
At an action level, additionally enforce users in the role "admin"
You want to replace the controller constraint at the action level
You want to remove the controller constraint at the action level and make the method available to anonymous users
You didn't specify your MVC version, so I will assume the latest as of today (MVC 4.5). However, that won't change of the answer much even if you were using MVC 3.
[Anonymous] overrides controller [Authorize] (case 3)
Case 3. I don't need to cover (the use of [AllowAnonymous]) as it has been answered all over SO and all over the web already. Suffice to say: if you specify [AllowAnonymous] on an action it will make that action public even if the controller has [Authorize] on it.
You can also make an entire website subject to authorisation by using a global filter, and use AllowAnonymous on the few actions or controllers you want to make public.
[Authorize] is additive (case 1)
Case 1 is easy. Take the following controller as an example:
[Authorize(Roles="user")]
public class HomeController : Controller {
public ActionResult AllUsersIndex() {
return View();
}
[Authorize(Roles = "admin")]
public ActionResult AdminUsersIndex() {
return View();
}
}
By default [Authorize(Roles="user")] makes all Actions in the Controller available to accounts in the "user" role only. Therefore to access AllUsersIndex you must be in the "user" role. However to access AdminUsersIndex you must be both in the "user" and the "admin" role. For example:
UserName: Bob, Roles: user, cannot access AdminUsersIndex, but can access AllUsersIndex
UserName: Jane, Roles: admin, cannot access AdminUsersIndex or AllUsersIndex
UserName: Tim, Roles: user & admin, can access AdminUsersIndex and AllUsersIndex
This illustrates that the [Authorize] attribute is additive. This is also true of the Users property of the attribute, which can be combined with Roles to make it even more restrictive.
This behaviour is due to the way that controller and action attributes work. The attributes are chained together and applied in the order controller then action. If the first one refuses authorization, then control returns and the action's attribute is not called. If the first one passes authorization, then the second one is then checked as well. You can override this order by specifying Order (for example [Authorize(Roles = "user", Order = 2)]).
Overriding [Authorize] (case 2)
Case 2 is trickier. Recall from above that the [Authorize] attributes are examined in the order (Global then) Controller then Action. The first one to detect that the user is ineligible to be authorized wins, the others don't get called.
One way around this is to define two new attributes as below. The [OverrideAuthorize] does nothing other than defer to [Authorize]; its only purpose is to define a type that we can check for. The [DefaultAuthorize] allows us to check to see if the Action being called in the request is decorated with a [OverrideAuthorize]. If it is then we defer to the Action authorization check, otherwise we proceed with the Controller level check.
public class DefaultAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if (action.IsDefined(typeof(OverrideAuthorizeAttribute), true)) return;
base.OnAuthorization(filterContext);
}
}
public class OverrideAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
We can then use it like this:
[DefaultAuthorize(Roles="user")]
public class HomeController : Controller {
// Available to accounts in the "user" role
public ActionResult AllUsersIndex() {
return View();
}
// Available only to accounts both in the "user" and "admin" role
[Authorize(Roles = "admin")]
public ActionResult AdminUsersIndex() {
return View();
}
// Available to accounts in the "superuser" role even if not in "user" role
[OverrideAuthorize(Roles = "superuser")]
public ActionResult SuperusersIndex() {
return View();
}
}
In the above example SuperusersIndex is available to an account that has the "superuser" role, even if it does not have the "user" role.
I would like to add something to Overriding [Authorize] (case 2)
OverrideAuthorizeAttribute and DefaultAuthorizeAttribute works fine, but I discover that you can also use
OverrideAuthorizationAttribute which overrides authorization filters defined at a higher level.
[Authorize(Roles="user")]
public class HomeController : Controller {
// Available to accounts in the "user" role
public ActionResult AllUsersIndex() {
return View();
}
// Available only to accounts both in the "user" and "admin" role
[Authorize(Roles = "admin")]
public ActionResult AdminUsersIndex() {
return View();
}
// Available to accounts in the "superuser" role even if not in "user" role
[OverrideAuthorization()]
[Authorize(Roles = "superuser")]
public ActionResult SuperusersIndex() {
return View();
}
}
If use it on controller then, all methods of this controller will effected.
[Authorize]
public class SomeController(){
// all actions are effected
public ActionResult Action1
public ActionResult Action2
If you want to prevent for one of these actions, you can use something like this:
[Authorize]
public class SomeController(){
// all actions are effected
public ActionResult Action1
public ActionResult Action2
[AllowAnonymous]
public ActionResult Action3 // only this method is not effected...
I made an adaptation of this answer's second case for ASP.NET Core 2.1.
The difference with ASP.NET Core's AuthorizeAttribute is that you don't have to call AuthorizeAttribute.OnAuthorization base method to proceed to normal authorization. This means that even if you don't explicitly call the base method, the base AuthorizeAttribute could still short-circuit authorization by forbidding access.
What I did is that I created a DefaultAuthorizeAttribute that does not inherit from AuthorizeAttribute, but from Attribute instead. Since the DefaultAuthorizeAttribute does not inherit from AuthorizeAttribute, I had to recreate the authorization behavior.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class DefaultAuthorizeAttribute : Attribute, IAuthorizationFilter
{
private readonly AuthorizeFilter m_authorizeFilter;
public DefaultAuthorizeAttribute(params string[] authenticationSchemes)
{
var policyBuilder = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(authenticationSchemes)
.RequireAuthenticatedUser();
m_authorizeFilter = new AuthorizeFilter(policyBuilder.Build());
}
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
if (filterContext.ActionDescriptor is ControllerActionDescriptor controllerAction
&& controllerAction.MethodInfo.GetCustomAttributes(typeof(OverrideAuthorizeAttribute), true).Any())
{
return;
}
m_authorizeFilter.OnAuthorizationAsync(filterContext).Wait();
}
}
public class OverrideAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext filterContext) { }
}
There is an authorize attribute on top of my contoller that means it contains all of my actions.
I want to exclude some actions from this attribute (these actions be available by anonymous users). Is it possible?
[Authorize]
public class TestController : Controller
{
public ActionResult Index()
{
...
}
...
//available by anonymous
public ActionResult Test()
{
...
}
}
You can take the approach outlined in this blog post of creating an AllowAnonymous attribute and placing this attribute on actions you wish to exclude:
http://blogs.msdn.com/b/rickandy/archive/2011/05/02/securing-your-asp-net-mvc-3-application.aspx
As of MVC 4, the AllowAnonymous attribute is stock and can be applied as needed.
Putting the [Authorize] attribute on the controller is basically a shortcut to putting it on every action, so your code is logically equivalent to
// No [Authorize] here
public class TestController : Controller
{
[Authorize]
public ActionResult Index()
{
// code here...
}
[Authorize]
public ActionResult Test()
{
// code here...
}
}
You can probably see where I'm going with this - remove the attribute from the controller, and put it on the specific actions that you want to be restricted:
// No [Authorize] here
public class TestController : Controller
{
[Authorize]
public ActionResult Index()
{
// code here...
}
// no [Authorize] here either, so anonymous users can access it...
public ActionResult Test()
{
// code here...
}
}
You might want to put the attribute on top of the restricted actions and leave the others (the ones in which you want to allow anonymous access) alone.
Also take it out of the top of the class.
I see that version 2 of MVC.NET now has a RequireHttps attribute, which works great for me. However, what's a good strategy for turning the effect off? For example, I want to use Https on some pages, but regular Http on others. Should I create my own RequireHttp attribute?
EDIT: I'm using my own RequireHttp attribute, and it works fine, but I'm wondering if there's some built-in functionality in MVC.NET Version 2 that I'm missing.
EDIT 2: I must not have been clear. My question concerns the following: if you use RequireHttps, then any requests after that will be over Https even if the Controller or Action is not decorated with RequireHttps. Unless I'm mistaken, you need a 2nd attribute such as RequireHttp to redirect requests to Http instead of Https.
The point of the ActionFilterAttribute is that you can apply them to any actions you want. Or in other words, you don't have to apply them to all the actions.
If you don't want an attribute's logic injected into an action, then simply don't apply the attribute to it. For example :
public class SomeController : Controller {
[RequireHttps]
public ActionResult SomeAction() {
//the attribute's logic will be injected to this action.
return View();
}
public ActionResult SomeOtherAction() {
//this action doesn't require https protocol
return View();
}
}
If you apply the attribute to the controller itself, then it will be applied to all the actions in the controller.
Edit :
To require http protocol instead of https, I think you can use the attribute below. I will double check to see if MVC 2 has this already. But if it doesn't (It doesn't) :
public class RequireHttp : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Request.IsSecureConnection) {
UriBuilder builder = new UriBuilder() {
Scheme = "http",
Host = filterContext.HttpContext.Request.Url.Host,
Path = filterContext.HttpContext.Request.RawUrl
};
filterContext.Result = new RedirectResult(builder.ToString());
filterContext.Result.ExecuteResult(filterContext);
}
base.OnActionExecuting(filterContext);
}
}
You could apply the attribute either at the controller in which case it will apply for all actions, or only on selected actions.
//apply to all actions
[RequireHttps]
public class SomeController
{
//apply to this action only
[RequireHttps]
public ActionResult SomeAction()
{
}
}
It worked for me!
I converted the çağdaş answer to Visual Basic:
Public Class RequireHttpAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuting(ByVal filterContext As _
ActionExecutingContext)
If (filterContext.HttpContext.Request.IsSecureConnection) Then
Dim builder As UriBuilder = New UriBuilder()
builder.Scheme = "http"
builder.Host = filterContext.HttpContext.Request.Url.Host
builder.Path = filterContext.HttpContext.Request.RawUrl
filterContext.Result = New RedirectResult(builder.ToString())
filterContext.Result.ExecuteResult(filterContext)
End If
MyBase.OnActionExecuting(filterContext)
End Sub
End Class
I use it like this:
<RequireHttp()> _
Public Class SomeController
<RequireHttp()> _
Function SomeAction(...) As ActionResult
...
End Function
End Class
I'm also using Joel Mueller's RemoteRequireHttps described here:
ASP.NET MVC RequireHttps in Production Only
[RequireHttps] //apply to all actions in controller
public class SomeController
{
//... or ...
[RequireHttps] //apply to this action only
public ActionResult SomeAction()
{
}
}
Applying it at the controller level also applies it to all action methods, otherwise apply it to each individual Action method.