I'm setting up a role-based authorization in my ASP.NET MVC Web application and I need help with role authorization attributes at different levels.
I've read about it from this article and saw that it is possible to have limits like in this example code:
[Authorize(Roles = "Administrator, User")]
public class MyController : Controller
{
public ActionResult SetAction1()
{
}
[Authorize(Roles = "Administrator")]
public ActionResult SetAction2()
{
}
}
Is it possible to have this kind of role authorization?
[Authorize(Roles = "Administrator")]
public class ControlPanelController : Controller
{
public ActionResult SetAction1()
{
}
[Authorize(Roles = "Administrator, User")]
public ActionResult SetAction2()
{
}
}
Nope;
When you set [Authorize(Roles = "Administrator")] on controller, then Only
an Administrator can pass the authorization and User role will be prevented. So, User will never reach to that level. But if one user have both Administrator and User role at the same time then S/he will pass it.
Related
There are two roles in my application: Admin and ContentMaker. Admin has full access and ContentMaker can create\edit articles. One user has one role (one-to-one).
Although i have only one AdminController for all stuff (include manage articles) decorated with AuthorizeAttribute. I want to grant access only to edit articles for ContentMaker within AdminController. The problem is contentmaker IS NOT admin so he has no access to AdminController in all. Is it possible to extend AuthorizeAttribute to let this behavior? This is what i want:
//only Admin can access this controller
[Authorize(Roles = ConstantsWeb.Database.AdminRoleName)]
public partial class AdminController : Controller
{
//ContentMaker has no access here
public ActionResult SomeAdminStuffAction()
{
//code
}
//ContentMaker only has access here, although he is not Admin
[Authorize(Roles = ConstantsWeb.Database.ContentMakerRoleName)]
public ActionResult EditArticle(int id)
{
//code
}
}
It is easy solution. Since ASP.NET MVC 5 here is OverrideAuthorization attribute. It let to override access permissions for any actions in controller. You need to put OverrideAuthorization attribute and another Authorize attribute. Like this:
//only Admin can access this controller
[Authorize(Roles = ConstantsWeb.Database.AdminRoleName)]
public partial class AdminController : Controller
{
//ContentMaker has no access here
public ActionResult SomeAdminStuffAction()
{
//code
}
//ContentMaker only has access here, although he is not Admin
[OverrideAuthorization]
[Authorize(Roles = ConstantsWeb.Database.AdminRoleName + "," + ConstantsWeb.Database.ContentMakerRoleName)]
public ActionResult EditArticle(int id)
{
//code
}
}
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've created controller classes to assist with Role authorization.
I have a base class ControllersAuthorities, which is the highest level of authority. I have created the other classes to extend each base class.
[Authorize(Roles = "Owner")]
public abstract class ControllerAuthorities:Controller { }
[Authorize(Roles = "Admin")]
public abstract class AdminController:ControllerAuthorities { }
[Authorize(Roles = "Employee")]
public abstract class EmployeeController:AdminController { }
[Authorize(Roles = "Sales")]
public abstract class SalesController:EmployeeController { }
First question, will the Owner, Admin and Employee Roles have access to the SalesController?
When implementing these classes in my project controllers.
If I leave the [Authorize] uncommented, will this override the inherited authority Role?
//[Authorize]
public class AccountController:ControllerAuthorities
{
Looking at AttributeUsage attribute of Authorize attribute ;
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
Inherited= true means that subclasses of the class which decorated with this attribute can inherit this attribute.
AllowMultiple=true means that this attribute can be placed more than once on same entity.
With inherited attributes and allowed usage of same attribute your SalesController can be considered as
[Authorize(Roles = "Sales")]
[Authorize(Roles = "Employee")]
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Owner")]
public abstract class SalesController:EmployeeController { }
And you can test this at runtime with this code.
var a = typeof(SalesController).GetCustomAttributes(true).ToArray();
First question, will the Owner, Admin and Employee Roles have access to the SalesController?
Inherited attributes are separated so they are applied independently.For one user to access SalesController , user must have all roles(owner ,admin ,employee and sales) not one of them.
See the difference between
[Authorize(Roles = "Sales")]
[Authorize(Roles = "Employee")]
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Owner")]
public abstract class SalesController:EmployeeController { }
and
[Authorize(Roles = "Owner,Admin,Employee,Sales")]
public abstract class SalesController:EmployeeController { }
Second question: If you leave [Authorize] uncommented with same logic AccountController is like
[Authorize(Roles = "Owner")]
[Authorize]
public class AccountController:ControllerAuthorities{}
So it does not override inherited authority just creates multiple usage of authorize attribute because multiple usage is allowed for Authorize attribute. If AllowMultiple were false in Authorize attribute definiton then derived class could override the attribute in base class.
will the Owner, Admin and Employee Roles have access to the
SalesController?
No, They can't access to SalesController. Inheritance makes your code like this:
[Authorize(Roles = "Owner")]
public abstract class ControllerAuthorities:Controller { }
[Authorize(Roles = "Admin", "Owner")]
public abstract class AdminController:Controller { }
[Authorize(Roles = "Employee", "Admin", "Owner")]
public abstract class EmployeeController:Controller { }
[Authorize(Roles = "Sales", "Employee", "Admin", "Owner")]
public abstract class SalesController:Controller { }
And since SalesController requires additional role, named Sales won't be accessible. Key to Access SalesController: The user should be in All the mentioned roles.
If I leave the [Authorize] uncommented, will this override the
inherited authority Role?
Yes, since AccountController derived from ControllerAuthorities which requires Owner role.
Note that the controllers in MVC are just classes with some additional features to handle requests. There's no difference with class concepts.
Tip : Look at the followings:
[Authorize(Roles = "Sales, Employee, Admin, Owner")] allows the
user which have one of the roles. In another words, This acts
like OR (||) operation.
[Authorize(Roles = "Sales", "Employee", "Admin", "Owner")] allows
the user which have All of the roles. In another words, This acts
like And (&) operation.
The last one is like your question. That's equal to the following too:
[Authorize(Roles = "Owner")]
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Employee")]
[Authorize(Roles = "Sales")]
For more clarification than this! see How to authorize a set of controllers without placing the annotation on each one?
How does the AuthorizeAttribute work?
[Authorize]
public ActionResult AuthenticatedUsers()
{
return View();
}
[Authorize(Roles = "Admin, Super User")]
public ActionResult AdministratorsOnly()
{
return View();
}
It like be a Session
[Authorize] means if allow all authenticated users
[Authorize(Roles = "Admin, Super User")] means if allow only roles person
for example admin see everything in page, but same page user see some content.
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) { }
}