Authorize Attribute with Multiple Roles

95,502

Solution 1

Try to create custom authorize attribute like this.

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    public AuthorizeRolesAttribute(params string[] roles) : base()
    {
        Roles = string.Join(",", roles);
    }
}

Assuming your roles will be the same for multiple controllers, create a helper class:

public static class Role
{
    public const string Administrator = "Administrator";
    public const string Assistant = "Assistant";
}

Then use it like so:

public class MyController : Controller
{
    [AuthorizeRoles(Role.Administrator, Role.Assistant)]
    public ActionResult AdminOrAssistant()
    {                       
        return View();
    }
}

Solution 2

The best and simplest way I found to resolve this problem is just to concatenate roles in the Authorize attribute.

[Authorize(Roles = CustomRoles.Admin + "," + CustomRoles.OtherRole)]

with CustomRole a class with constant strings like this :

public static class CustomRoles
{
    public const string Admin = "Admin";
    // and so on..
}

Solution 3

Make sure you are deriving your custom attribute class off System.Web.Mvc.AuthorizeAttribute and NOT System.Web.Http.AuthorizeAttribute.

I ran into the same problem. Once I changed it, everything worked.

You may also want to add the following to your custom attribute class:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 

Solution 4

What i did is the answer in @Tieson

I tweak a little in his answer. Instead of string.Join why not convert it to list?

Here is my answer:

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    private new List<string> Roles;
    public AuthorizeRolesAttribute(params string[] roles) : base()
    {
        Roles = roles.toList()
    }
}

And then check the if the role is valid overriding OnAuthorization

public override void OnAuthorization(HttpActionContext actionContext)
{
            if (Roles == null)
                HandleUnauthorizedRequest(actionContext);
            else
            {
                ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
                string _role = claimsIdentity.FindFirst(ClaimTypes.Role).Value;
                bool isAuthorize = Roles.Any(role => role == _role);

                if(!isAuthorize)
                    HandleUnauthorizedRequest(actionContext);
            }
        }

And there you have it, it is now validating if the role is authorized to access the resource

Solution 5

I feel like a custom authorize attribute is overkill for this issue unless you have a large amount of roles.

Since the string must be known at compile time, why not make a static Role class that contains public strings of the roles you have defined, and then add comma separated strings with certain roles that you want to authorize:

public static class Roles
{
    public const string ADMIN = "Admin";
    public const string VIEWER = "Viewer";

    public const string ADMIN_OR_VIEWER = ADMIN + "," + VIEWER;
}

And then you can use the Authorize Attribute like so on the Controller Class or the Controller Method (or both):

[Authorize(Roles = Roles.ADMIN]
public class ExampleController : Controller
{
    [Authorize(Roles = Roles.ADMIN_OR_VIEWER)
    public ActionResult Create()
    {
        ..code here...
    }
}
Share:
95,502

Related videos on Youtube

Christian Sauer
Author by

Christian Sauer

SOreadytohelp

Updated on February 13, 2021

Comments

  • Christian Sauer
    Christian Sauer about 3 years

    I would like to add Authorization to a controller, for multiple Roles at once.

    Normally that would look like this:

    [Authorize(Roles = "RoleA,RoleB,RoleC")]
    public async Task<ActionResult> Index()
    {
    }
    

    But I have stored my Roles in consts, since they might change or be extended at some point.

    public const RoleA = "RoleA";
    public const RoleB = "RoleB";
    public const RoleC = "RoleC";
    

    I cannot do this, since the string must be known at compile time:

    [Authorize(Roles = string.join(",",RoleA,RoleB,RoleC)]
    public async Task<ActionResult> Index()
    {
    }
    

    Is there a way to circumvent the problem?

    I COULD write a const which simply contains "RoleA,RoleB,RoleC" - but I dislike magic strings and this is a magic string. Changing the name of a Role and forgetting to change the combined string would be a disaster.

    I am using MVC5. ASP.NET Identity and the Role are known at compile time.

  • Christian Sauer
    Christian Sauer almost 10 years
    Now that's an idea worthy of Mac Gyver ;)
  • sshine
    sshine over 8 years
    I also like this solution a lot, especially because I can let my Role be an enum rather than a string. What would a good namespace and location in the project hierarchy be for placing this custom authorize attribute?
  • MacGyver
    MacGyver over 8 years
    @SimonShine - I believe that, you should place it in web project e.g. in Attributes folder. However if you have multiple projects in one solution you'll need to move this to another library visible for every project.
  • sshine
    sshine over 8 years
    @MacGyver: An Attributes folder seems like the logical choice, although I don't know if I'll be adding enough attributes to justify creating a top-level directory yet. What to name such directory is also discussed in stackoverflow.com/questions/7163698/…
  • MacGyver
    MacGyver over 8 years
    @SimonShine Hey, nothing is stopping you to place this class wherever you want. Unless it's not used in views - there is always a refactoring.
  • Urielzen
    Urielzen about 8 years
    I am not sure what is going on here, but this did NOT help me, any user regardless of the role was able to access the method.
  • Chef_Code
    Chef_Code about 8 years
    Very Nice SIR, Works like a charm
  • GhostCat
    GhostCat about 7 years
    Valueable; but this should be a comment; not an answer.
  • John Leidegren
    John Leidegren over 6 years
    This example doesn't work, or at least not the way you might think. For example, while novel the ADMIN_OR_VIEWER role on the action is redundant because you will not be allowed to get to the Create method if you don't already have the ADMIN role. In this case VIEWER will never be able to invoke Create method.
  • EduLopez
    EduLopez over 6 years
    This solution is not scalable too. There will be a point where you have too many roles with different actions and you shouldnt create every combination
  • fraser jordan
    fraser jordan about 6 years
    I just tried this and found referencing the library System.Web.Http.AuthorizeAttribute INSTEAD OF System.Web.Mvc.AuthorizeAttribute
  • Eric Eskildsen
    Eric Eskildsen over 5 years
    Both your answer and the accepted answer will trigger authorization if implemented correctly (I'm using the accepted in a production web app). Proposing an edit to remove the comments about the accepted answer.
  • RJB
    RJB over 4 years
    Same issue as @Urielzen, but it was fixed by the answer below from Jerry Finegan (using "System.Web.Mvc.AuthorizeAttribute and NOT System.Web.Http.AuthorizeAttribute")
  • Varun
    Varun over 3 years
    If the Roles were Enums then you can use something like, [Authorize(Roles = nameof(UserRoleEnum.User) + "," + nameof(UserRoleEnum.Admin))]
  • Tom Lint
    Tom Lint almost 3 years
    @JohnLeidegren this is incorrect. The action-level AuthorizeAttribute overrides the controller-level AuthorizeAttribute, otherwise, you would not be able to have actions decorated with AllowAnonymousAttribute in a controller with an AuthorizeAttribute