Reputation: 12583
Right now I decorate a method like this to allow "members" to access my controller action
[Authorize(Roles="members")]
How do I allow more than one role? For example the following does not work but it shows what I am trying to do (allow "members" and "admin" access):
[Authorize(Roles="members", "admin")]
Upvotes: 333
Views: 270887
Reputation: 71
If you have anything beyond a basic Role based security you will have custom authorization attributes. It’s harder to have multiple “OR”ing authorize filter attributes.
I wanted:
"somethingIDontHave.oops" | "SRE.View" | "SRE.List" | "SRE-BADBADBAD.List" | "SRE.Edit"
Where those are custom filters and I wanted them in their own Attributes.
At our company we have Resources and Actions that you can perform as well as Phylums that you can be. It’s a little more complicated than just Roles.
I wanted something like this:
[AuthorizeByResourceAction("somethingIDontHave", "oops")]
[AuthorizeByResourceAction("SRE", "View")]
[AuthorizeByResourceAction("SRE-BADBADBAD", "List")]
[AuthorizeByResourceAction("SRE", "Edit")]
public class DashboardModel : PageModel
{}
And have each AuthorizeByResourceAction OR each other.
My solution is to have OnAuthorizationAsync get all the attributes on the Target and then run over all of them until I get a ‘Yes’. You will get check amplification with this solution, so you should be caching your results. You also have to pass in the type.
How to specify which attributes can be OR’ed.
public interface IOrAbleAuthorization
{
Task<bool> IsAuthorizedAsync(HttpContext httpContextAccessor);
}
For the actual class that does the Authorization Filtering
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorizeByResourceActionAttribute : Attribute, IAsyncAuthorizationFilter, IOrAbleAuthorization
{
protected readonly string _resource;
protected readonly string _action;
protected readonly Type _controller;
public AuthorizeByResourceActionAttribute(Type Controller, string resource, string action = null)
{
_resource = resource;
_action = action;
_controller = Controller;
}
}
The code that does the actual custom authorization is part of the Interface's IsAuthorizedAsync instead of OnAuthorizationAsync. OnAuthorizationAsync is responsible for calling all the other IOrAbleAuthorization classes.
public async Task<bool> IsAuthorizedAsync(HttpContext httpContextAccessor)
{
// Custom Auth Code here
string ActorJWT = httpContextAccessor.GetJWTFromCookie(false);
bool hasAuth = await PM.Instance.HasPermission(ActorJWT, _resource, _action);
if ( !hasAuth)
{
L.Warn($"User does not have permission to {_resource}.{_action}");
return false;
}
return true;
}
That is a simple true/false for IsAuthorized.
OnAuthorizationAsync will iterate over all of the attached attributes looking for any "True/Yes".
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
object[] attributes = _controller.GetCustomAttributes(false);
IOrAbleAuthorization[] filteredAttributes = attributes
.Where(attribute => attribute.GetType().GetInterfaces().Contains(typeof(IOrAbleAuthorization)))
.Cast<IOrAbleAuthorization>()
.ToArray();
bool isAuthorized = false;
foreach(IOrAbleAuthorization filteredAttribute in filteredAttributes)
{
isAuthorized = await filteredAttribute.IsAuthorizedAsync(context.HttpContext);
if ( isAuthorized )
break;
}
if ( !isAuthorized) // All of them were false
{
context.Result = new ForbidResult();
}
}
I could not find a way to get the Target type in my attribute without passing it in directly. So the final call will look like this:
[AuthorizeByResourceAction(typeof(DashboardModel), "somethingIDontHave", "oops")]
[AuthorizeByResourceAction(typeof(DashboardModel), "SRE", "View")]
[AuthorizeByResourceAction(typeof(DashboardModel), "SRE", "List")]
[AuthorizeByResourceAction(typeof(DashboardModel), "SRE.History", "List")]
[AuthorizeByResourceAction(typeof(DashboardModel), "SRE", "Edit")]
public class DashboardModel : PageModel
{ }
Problems:
You can see the call amplification here with 5 calls for the Auth check for "somethingIDontHave", but zero for "SRE-BADBADBAD". This is because "somethingIDontHave" is first, and the identity doesn't have that Resource. But the identity does have the "SRE.View" ResourceAction so it doesn't need to proceed to the following Authorization filter checks.
I would put in caching here to alleviate the multiple same checks.
I also don't like the passing in of the type. That's error-prone when copy/pasta the AuthorizeByResourceAction attribute onto the next controller/page.
Upvotes: 0
Reputation: 1980
For "Or" you need comma between roles
[Authorize(Roles="members,admin")]
both members and admins can access
But for "and" you need
[Authorize(Roles="manager")]
[Authorize(Roles="sales")]
managers who are in sales can access.
Upvotes: 5
Reputation: 59
I mixed answers and proposed this method.
Firstly, We create an enum for role accesses.
public enum ERoleAccess
{
[Description("Admin User")]
Admin = 1,
[Description("General User")]
User = 2,
[Description("Editor User")]
Editor = 3,
}
Secondly, we need an attribute filter for customer MVC authorize.
public class RolesAttribute:AuthorizeAttribute
{
public RolesAttribute(params ERoleAccess[] roles)
{
Roles = string.Join(",", roles);
}
}
Finally, we can use "RolesAttribute" on the controllers or actions.
[Roles(ERoleAccess.Admin, ERoleAccess.Editor, ERoleAccess.User)]
In this approach, we use numbers of alternative string values. (1= Admin, 2=User,...)
It's good for decreasing token size and comparing performance.
Upvotes: 4
Reputation: 2322
[Authorize(Roles="admin")]
[Authorize(Roles="members")]
will work when the AND
is needed (as asked by question) whereas this answer shows the OR
version.
See more at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0#adding-role-checks
Upvotes: 0
Reputation: 601
You can use Authorization Policy in Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("admin", policy => policy.RequireRole("SuperAdmin","Admin"));
options.AddPolicy("teacher", policy => policy.RequireRole("SuperAdmin", "Admin", "Teacher"));
});
And in Controller Files:
[Authorize(Policy = "teacher")]
[HttpGet("stats/{id}")]
public async Task<IActionResult> getStudentStats(int id)
{ ... }
"teacher" policy accept 3 roles.
Upvotes: 13
Reputation: 1
Intent promptInstall = new Intent(android.content.Intent.ACTION_VIEW);
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
promptInstall.setDataAndType(Uri.parse("http://10.0.2.2:8081/MyAPPStore/apk/Teflouki.apk"), "application/vnd.android.package-archive" );
startActivity(promptInstall);
Upvotes: -11
Reputation: 7059
Another option is to use a single authorize filter as you posted but remove the inner quotations.
[Authorize(Roles="members,admin")]
Upvotes: 693
Reputation: 2854
Using AspNetCore 2.x, you have to go a little different way:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeRoleAttribute : AuthorizeAttribute
{
public AuthorizeRoleAttribute(params YourEnum[] roles)
{
Policy = string.Join(",", roles.Select(r => r.GetDescription()));
}
}
just use it like this:
[Authorize(YourEnum.Role1, YourEnum.Role2)]
Upvotes: 4
Reputation: 2042
If you find yourself applying those 2 roles often you can wrap them in their own Authorize. This is really an extension of the accepted answer.
using System.Web.Mvc;
public class AuthorizeAdminOrMember : AuthorizeAttribute
{
public AuthorizeAdminOrMember()
{
Roles = "members, admin";
}
}
And then apply your new authorize to the Action. I think this looks cleaner and reads easily.
public class MyController : Controller
{
[AuthorizeAdminOrMember]
public ActionResult MyAction()
{
return null;
}
}
Upvotes: 3
Reputation: 5920
If you want use custom roles, you can do this:
CustomRoles
class:
public static class CustomRoles
{
public const string Administrator = "Administrador";
public const string User = "Usuario";
}
Usage
[Authorize(Roles = CustomRoles.Administrator +","+ CustomRoles.User)]
If you have few roles, maybe you can combine them (for clarity) like this:
public static class CustomRoles
{
public const string Administrator = "Administrador";
public const string User = "Usuario";
public const string AdministratorOrUser = Administrator + "," + User;
}
Usage
[Authorize(Roles = CustomRoles.AdministratorOrUser)]
Upvotes: 164
Reputation: 29
Better code with adding a subclass AuthorizeRole.cs
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
class AuthorizeRoleAttribute : AuthorizeAttribute
{
public AuthorizeRoleAttribute(params Rolenames[] roles)
{
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "Unauthorized" },
{ "controller", "Home" },
{ "area", "" }
}
);
//base.HandleUnauthorizedRequest(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "Login" },
{ "controller", "Account" },
{ "area", "" },
{ "returnUrl", HttpContext.Current.Request.Url }
}
);
}
}
}
How to use this
[AuthorizeRole(Rolenames.Admin,Rolenames.Member)]
public ActionResult Index()
{
return View();
}
Upvotes: 3
Reputation: 57
Another clear solution, you can use constants to keep convention and add multiple [Authorize] attributes. Check this out:
public static class RolesConvention
{
public const string Administrator = "Administrator";
public const string Guest = "Guest";
}
Then in the controller:
[Authorize(Roles = RolesConvention.Administrator )]
[Authorize(Roles = RolesConvention.Guest)]
[Produces("application/json")]
[Route("api/[controller]")]
public class MyController : Controller
Upvotes: 4
Reputation: 344
For MVC4, using a Enum
(UserRoles
) with my roles, I use a custom AuthorizeAttribute
.
On my controlled action, I do:
[CustomAuthorize(UserRoles.Admin, UserRoles.User)]
public ActionResult ChangePassword()
{
return View();
}
And I use a custom AuthorizeAttribute
like that:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class CustomAuthorize : AuthorizeAttribute
{
private string[] UserProfilesRequired { get; set; }
public CustomAuthorize(params object[] userProfilesRequired)
{
if (userProfilesRequired.Any(p => p.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("userProfilesRequired");
this.UserProfilesRequired = userProfilesRequired.Select(p => Enum.GetName(p.GetType(), p)).ToArray();
}
public override void OnAuthorization(AuthorizationContext context)
{
bool authorized = false;
foreach (var role in this.UserProfilesRequired)
if (HttpContext.Current.User.IsInRole(role))
{
authorized = true;
break;
}
if (!authorized)
{
var url = new UrlHelper(context.RequestContext);
var logonUrl = url.Action("Http", "Error", new { Id = 401, Area = "" });
context.Result = new RedirectResult(logonUrl);
return;
}
}
}
This is part of modifed FNHMVC by Fabricio Martínez Tamayo https://github.com/fabriciomrtnz/FNHMVC/
Upvotes: 19
Reputation: 2192
One possible simplification would be to subclass AuthorizeAttribute
:
public class RolesAttribute : AuthorizeAttribute
{
public RolesAttribute(params string[] roles)
{
Roles = String.Join(",", roles);
}
}
Usage:
[Roles("members", "admin")]
Semantically it is the same as Jim Schmehil's answer.
Upvotes: 107