programing

How can we set authorization for a whole area in ASP.NET MVC?

sourcetip 2021. 1. 17. 12:38
반응형

How can we set authorization for a whole area in ASP.NET MVC?


I've an Admin area and I want only Admins to enter the area. I considered adding the Authorized attribute to every controller in the Admin area. Isn't there an elegant solution or is this feature not there in the framework itself?

EDIT: I'm sorry, I should to have mentioned this before. I'm using a custom AuthorizedAttribute derived from AuthorizeAttribute.


Web.config-based security should almost never be used in an MVC application. The reason for this is that multiple URLs can potentially hit a controller, and putting these checks in Web.config invariably misses something. Remember - controllers are not associated with areas, routes are associated with areas. The MVC controller factory will happily serve controllers from the Areas/ folder for non-area requests if there's no conflict.

For example, using the default project structure, adding an Admin area with an AdminDefaultController, you can hit this controller via /Admin/AdminDefault/Index and /AdminDefault/Index.

The only supported solution is to put your attribute on a controller base class and to ensure that each controller within the area subclasses that base class.


I have just been investigating this same issue. Since it is not possible to secure controllers based on areas, a simpler option comes to mind.

Create a base controller definition for each area that overrides Controller, and add the security require to this. Then you just have to ensure each controller in the area overrides AreaController instead of Controller. For example:

/// <summary>
/// Base controller for all Admin area
/// </summary>
[Authorize(Roles = "Admin")]
public abstract class AdminController : Controller { }

It does still require that you derive each controller in the Admin area from this base,

public class HomeController : AdminController
{
    // .. actions
}

but at least you have a single point where you define the security for the area.


I just started on this... but so far this is working pretty good for me.

I create a custom AuthorizeAttribute class and add this in the RegisterGlobalFilters function.

In CustomAuthorizeAttribute I check for various conditions based on the area it is in.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomAuthorizeAttribute());
        filters.Add(new HandleErrorAttribute());
    }
}

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var routeData = httpContext.Request.RequestContext.RouteData;
        var controller = routeData.GetRequiredString("controller");
        var action = routeData.GetRequiredString("action");
        var area = routeData.DataTokens["area"];
        var user = httpContext.User;
        if (area != null && area.ToString() == "Customer")
        {
            if (!user.Identity.IsAuthenticated)
                return false;
        }
        else if (area != null && area.ToString() == "Admin")
        {
            if (!user.Identity.IsAuthenticated)
                return false;
            if (!user.IsInRole("Admin"))
                return false;
        }
        return true;
    }
}

If all of your admin code is in one controller then add Authorize to the entire class.

[Authorize]
public class AdminController : Controller
{
     .......
}

The currently accepted answer is not the most secure solution because it requires the developer to always remember to inherit that new base class for any new controllers or actions ("blacklisting"; allowing users access to everything unless an action is manually restricted). This especially causes problems when new developers, unacquainted with your rituals, are introduced to the project. It is easy to forget to inherit the proper controller class if done that way, especially after having taken your eyes off the project for weeks, months, or years. If a developer forgets to inherit, it isn't obvious that there is a security vulnerability in the project.

A more secure solution to this problem is to deny access to all requests, then decorate each action with the roles that are allowed access to the actions ("whitelisting"; preventing access to all users unless manually allowed). Now if a developer forgets to whitelist the proper authorization, the users will let you know and it's as simple as looking at other controllers for a reminder about how to give proper access. However, at least there is no major security vulnerability.

In App_Start/FilterConfig.cs file, modify the FilterConfig class:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        ...

        //Deny access to all controllers and actions so that only logged in Administrators can access them by default
        filters.Add(new System.Web.Mvc.AuthorizeAttribute() { Roles = "Administrator" });
    }

This makes all actions inaccessible unless the user is logged in as an Administrator. Then for each action that you want a different authorized user to have access to, you simply decorate it with [OverrideAuthorization] and [Authorize].

In your business logic, this allows you to use the Authorize attribute in a variety of ways without ever needing to worry about unauthorized users from accessing any functionality. Below are some examples.

Example 1 - Only logged in Administrator and Dispatcher users will be allowed to access Index() Get and Post methods.

public class MarkupCalculatorController : Controller //Just continue using the default Controller class.
{
    // GET: MarkupCalculator
    [OverrideAuthorization]
    [Authorize(Roles = "Administrator,Dispatcher")]
    public ActionResult Index()
    {
        //Business logic here.

        return View(...);
    }

    // POST: DeliveryFeeCalculator
    [HttpPost]
    [ValidateAntiForgeryToken]
    [OverrideAuthorization]
    [Authorize(Roles = "Administrator,Dispatcher")]
    public ActionResult Index([Bind(Include = "Price,MarkedupPrice")] MarkupCalculatorVM markupCalculatorVM)
    {
        //Business logic here.

        return View(...);
    }
}

Example 2 - Only authenticated users will be allowed to access the Home controller's Index() method.

public class HomeController : Controller
{
    [OverrideAuthorization]
    [Authorize] //Allow all authorized (logged in) users to use this action
    public ActionResult Index()
    {
        return View();
    }

}

Example 3 - Unauthenticated users (i.e. anonymous users) can be allowed to access methods by using the [AllowAnonymous] attribute. This also automatically overrides the global filter without needing the [OverrideAuthorization] attribute.

    // GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        ...
    }

Example 4 - Only admins will be allowed access to methods that lack the [Authorize] attribute.

public class LocationsController : Controller
{

    // GET: Locations
    public ActionResult Index()
    {
        //Business logic here.
        return View(...);
    }
}

Some notes.

You must use the [OverrideAuthorization] attribute if you want to limit the access to a particular action to specific roles. Otherwise, the [Authorize] attribute properties will be ignored and only the default role (Administrator in my example) will be allowed, even if you specify other roles (e.g. Dispatcher, etc.) because of the global filter. Any unauthorized users will be redirected to the login screen.

Using the [OverrideAuthorization] attribute causes the action to ignore the global filter you set. Therefore, you must reapply the [Authorize] attribute whenever you use the override so that the action remains secure.

Regarding whole areas and controllers

To restrict by areas, as you are asking, put the [OverrideAuthorization] and [Authorize] attributes on the controller instead of the individual actions.


.. very crudely I believe you want something like this?

Quick and dirty role management

[Authorize(Roles = "Admins")]
public ActionResult Register()
{
  ViewData["roleName"] = new SelectList(Roles.GetAllRoles(), "roleName");
  ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
  return View();
}

ReferenceURL : https://stackoverflow.com/questions/2319157/how-can-we-set-authorization-for-a-whole-area-in-asp-net-mvc

반응형