8

I have implemented my own custom Authorize attribute.

The attribute is applied both at the controller level and at the action level.

Here is an example of what I need to do:

[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
    [ClaimsAuthorize(Roles = "Administrators")]
    public ActionResult AdministrativeTask()
    {
        return View();
    }

    public ActionResult SomeOtherAction()
    {
        return View();
    }
}

Currently if a user has the Administrator Role but not the AdvancedUsers role, he cannot execute "Administrative Task".

How can I change this behavior to perform a security check at the action level even if the user is not authorized at the controller level?

For the moment, the only solution I can think about is to implement 2 attributes: one for securing controllers, another for securing actions. Then I would play with the Order property to execute the one at the action level first.

However, I would prefer a solution with a single attribute if possible.

tereško
  • 58,060
  • 25
  • 98
  • 150
rlesias
  • 4,786
  • 3
  • 15
  • 20
  • 1
    By removing it from the controller level. Why do you want to have it at controller level when the action is the one that decides? – Wouter de Kort Oct 16 '13 at 12:16
  • I have not tested this, ever, but it's possible that by listing `[AllowAnonymous]` before the custom authorize attribute **on the action** that you'll get into the authorization for that action. – Mike Perrenoud Oct 16 '13 at 12:27
  • At the controller level you could check if the specific action method is decorated with your special attribute and then allow the method to be called – Wouter de Kort Oct 16 '13 at 12:28

5 Answers5

8

Use built-in [OverrideAuthorization]:

[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
    [OverrideAuthorization]
    [ClaimsAuthorize(Roles = "Administrators")]
    public ActionResult AdministrativeTask()
    {
        return View();
    }

    public ActionResult SomeOtherAction()
    {
        return View();
    }
}

OverrideAuthorization Attribute is available for MVC 5 (at least) and up. Once you decorate the Action with it, also decorate with the new Role and that will take effect over the Controller level Role.

Csaba Toth
  • 10,021
  • 5
  • 75
  • 121
2

To make specific actions restricted you simply use the Authorize-attribute on the methods that handle these actions. When you mark an action method with the Authorize attribute, access to that action method is restricted to users who are both authenticated and authorized.

     //[ClaimsAuthorize(Roles = "AdvancedUsers")]
     public class SecurityController : Controller
     {

        {
        [ClaimsAuthorize(Roles ="Administrators", "Role2","Role3")]
        public ActionResult AdministrativeTask()
        {
            return View();
        }
    }

OR you can override your authorization at controller level , Create a new OverrideAuthorizeAttribute attribute.

public class OverrideAuthorizeAttribute : AuthorizeAttribute {
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
    }
}

and you can use this attribute to override your controller level autorization.

[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
    [ClaimsAuthorize(Roles = "Administrators")]
    public ActionResult AdministrativeTask()
    {
        return View();
    }
     [OverrideAuthorizeAttribute(Roles ="xxxx")] // This role will override controller                   
                                                  //level authorization 
    public ActionResult SomeOtherAction()
    {
        return View();
    }
}
Suraj Singh
  • 4,041
  • 1
  • 21
  • 36
  • This would authorize Administrators on all the other actions. Something, I __don't__ want to do... – rlesias Oct 16 '13 at 12:46
  • That `OverrideAuthorizeAttribute` doesn't work for me, it doesn't actually override but the controller level role still also applies. – Csaba Toth Feb 21 '17 at 05:53
2

This should not be possible. Imagine the logic which MVC uses with the authorization filters.

  1. When the controller is determined - check if there is an authorization filter that applies to that controller and execute it.
  2. When the action is known - do the same for the action.

In all cases a fail in authorization would short-circuit the pipeline.

Ventsyslav Raikov
  • 6,882
  • 1
  • 25
  • 28
1

You need two authorization attributes - a base one with all authorization logic, and a second one, derived from the base attribute, that is only used to override the base attribute.

Example authorization attributes:

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
    protected bool _canOverride = true;

    //...custom authorization code goes here.....

    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        //Don't authorize if the override attribute exists
        if (_canOverride && actionContext.ActionDescriptor.GetCustomAttributes<OverrideClaimsAuthorizeAttribute>().Any())
        {
            return;
        }
        base.OnAuthorization(actionContext);
    }

}


public class OverrideClaimsAuthorizeAttribute : ClaimsAuthorizeAttribute
    {
        public OverrideClaimsAuthorizeAttribute ()
            : base()
        {
            _canOverride = false;
        }

    }

In the base authorization attribute we are saying to go ahead and authorize as normal, as long as the OverrideClaimsAuthorizeAttribute doesn't exist. If the OverrideClaimsAuthorizeAttribute does exist, then only run the authorization on classes where _canOverride is false (ie the OverrideClaimsAuthorizeAttribute class itself).

Example usage:

[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{

    //Ignores the controller authorization and authorizes with Roles=Administrators
    [OverrideClaimsAuthorize(Roles = "Administrators")]
    public ActionResult AdministrativeTask()
    {
        return View();
    }


    //Runs both the controller and action authorization, so authorizes with Roles=Administrators AND Roles=AdvancedUsers
    [ClaimsAuthorize(Roles = "Administrators")]
    public ActionResult AdvancedAdministrativeTask()
    {
        return View();
    }

    //authorizes with controller authorization: Roles=AdvancedUsers
    public ActionResult SomeOtherAction()
    {
        return View();
    }
}
Chris Coulson
  • 494
  • 3
  • 10
0

Check this previous question. (check @AndyBrown answer, case 2)

For a simple way you might also try adding ( [AllowAnonymous]) to override the controller [Authorize] then add a new custom filter to check for your logic for this particular action. Or you can add the code that checks for the role just inside it.

Community
  • 1
  • 1
A Khudairy
  • 1,422
  • 1
  • 15
  • 26
  • @AndyBrown's solution would work but it involves defining 2 attributes, something I would like to avoid. – rlesias Oct 16 '13 at 13:27
  • then add [AllowAnonymous], and check for the role you want inside the function (action) itself. I think that is as simple as it can get – A Khudairy Oct 16 '13 at 14:37