66

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();
  }

}
David Glenn
  • 23,572
  • 17
  • 70
  • 94

5 Answers5

118

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://docs.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() { ... }
}
Francisco Goldenstein
  • 11,917
  • 6
  • 48
  • 62
  • 3
    Perfect, just what was needed! – user1477388 Nov 18 '16 at 20:30
  • Note that this also suppresses global authentication filters. All global filters implementing IAuthorizationFilter will be disabled. – ajbeaven Mar 06 '17 at 20:52
  • any alternative in asp.net core ? – shashwat Mar 10 '17 at 10:21
  • 7
    This works great and i think it should be the selected answer. – Giannis Paraskevopoulos May 12 '17 at 09:00
  • 3
    Is there something similar in *Asp.Net Core*? – croxy Jun 22 '18 at 09:52
  • 3
    I edited my answer to add information about ASP.NET Core. – Francisco Goldenstein Jul 10 '18 at 15:56
  • It seems that you can make the action more secure only. So if controller accepts `Admin` or `Employee` role so the action can be restricted to only one of them. So you can't add `[Authorize(Roles = "Manager")]` and waiting that only `Manager` will be able to access it or even `Admin` or `Employee` or `Manager`. You answer and MS docs doesn't give an understanding about this point. At least for me – SerjG Dec 07 '18 at 11:23
  • 1
    @Sergey In Core you can make it more restrictive like I illustrated in the example but you cannot override the authorization with roles that are not part of the "original" list of roles. In the example I made it more restrictive by only allowing Admin to access ActionX. The idea is: if you have to control authorization for each action then you should separate the actions in different controllers. – Francisco Goldenstein Dec 07 '18 at 12:24
  • 1
    @FranciscoGoldenstein exactly. But for me it's not obvious and should be mentioned explicitly. – SerjG Dec 08 '18 at 14:27
28

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.

tvanfosson
  • 490,224
  • 93
  • 683
  • 780
  • In this case the CustomAuthorizeAttribute provides the same level of access but is used to compensate for a bug with Flash and so setting the Order properties is enough to achieve the desired result. Thanks. – David Glenn Jan 15 '10 at 15:04
  • By making the order of the attribute on the Action I was able to override the attribute on the Controller. Upvote! – David Hyde Nov 17 '17 at 10:02
13

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]
Omar Himada
  • 2,317
  • 1
  • 12
  • 27
bmavity
  • 2,207
  • 2
  • 23
  • 23
  • 3
    `IgnoreAuthorization`? You mean `AllowAnonymous`? – AgentFire Mar 02 '15 at 21:11
  • `AllowAnonymous` is what I needed. Thanks, @agentfire – Adam Prescott Jul 08 '15 at 13:42
  • Yeah, I was going to say, that exact logic is already built into AuthorizeAttribute's OnAuthorization method... except the attribute it's looking for is AllowAnonymous. However, this is a great illustration of how you can override the rigid "AND" logic that it normally applies. With this, you could apply an attribute of type FancyAuthorizeAttribute to the controller with one set of parameters, and then also apply it to a single controller method with different parameters, and it's OnAuthorization would detect the action-level attribute and skip the controller-level one. – Triynko Mar 02 '16 at 07:30
  • Actually, nevermind that last comment of mine, which probably wouldn't work. You'd need a separate class, since the logic in OnAuhorization would have no idea whether it's running for an attribute that's on the controller or an action method; in either case, they'd both see that an instance of itself exists on the action method and they'd both return without doing anything. That's sad. – Triynko Mar 02 '16 at 07:39
3

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.

Ciarán Bruen
  • 4,713
  • 13
  • 53
  • 65
MemeDeveloper
  • 5,845
  • 2
  • 38
  • 52
  • This advice is dangerous (at least for asp.net core 3.1) . `AllowAnonymous` also disables any following `Authorize` attributes. – myl Nov 13 '20 at 13:17
  • @myl ?? That's exactly the point! This is what the OP wants to do "Override the Authorize Attribute". "Dangerous" is a matter of opinion, arguably submitting any details on any web form to any server is "dangerous" as is crossing the road, or leaving the house. Assuming this is a Q&A site for programmers (presumably making software) I think that overriding a part of an application you helped author is not to automatically be considered overly "dangerous". Your comment (and downvote) suggest using AllowAnonymous is a bad idea - if so why did MS release it! My answer is precise and accurate. – MemeDeveloper Nov 29 '20 at 16:41
  • The user wishes to use a custom authorization policy. Adding a custom authorization attribute after a `[AllowAnonymous]` has no effect since `[AllowAnonymous]` wins (despite what one might have guessed). – myl Nov 30 '20 at 17:56
  • @myl please see my update (sample code). As you can see with a breakpoint inside OnAuthorization you are mistaken - this code is hit as expected. The OP is asking how to get their **custom Authorize attribute** to run for a single action if Authorize is used on controller. My example code now shows this exactly. – MemeDeveloper Dec 01 '20 at 08:33
  • @myl If the OP was asking if they could use the standard [Authorize] attribute with a custom policy parameter, e.g. [Authorize("BadgeEntry")] then this approach would not work. The policy would not be checked after adding AllowAnonymous to the action. But the OP asked and provided sample code indicating they have custom attribute, in which case my approach works, and is not in any way "dangerous". – MemeDeveloper Dec 01 '20 at 08:42
  • 1
    Note the asp.net core team _recommends_ using the "policy design" to implement custom authorization, however this question specifically relates to a **Custom Authorize Attribute** which is different, and there are plenty of use cases for this approach in asp.net core. More info here in the excellent answer from Derek Greer https://stackoverflow.com/a/41348219/661584 and documentation here https://docs.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-5.0 – MemeDeveloper Dec 01 '20 at 09:04
  • @myl if you don't know what you are doing, then I'd say doing _anything_ with Authentication & Authorization could be "dangerous", but in this case, I'd say the OP and other competent SO users are quite capable of producing a 100% "best practice" solution as sketched above (despite warnings in MS documentation). Personally I've been overriding and customizing plenty of asp.net forms & MVC (and recently aspnet core) "internal mechanics" inc. low level security features since ~2006, if you know what you are doing then you can often take a lot of MS documentation with a pinch of salt. Good luck! – MemeDeveloper Dec 01 '20 at 09:14
0

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());
        });