Search code examples
c#asp.netasp.net-identityauthorize-attributeblacklist

ASP.NET MVC Blacklist for Roles/Users


Question Summary: In ASP.NET MVC, is there a clean way to prevent a specific user or role from accessing an action?

Obviously, the following would allow roles Admin and Editor to access the entire controller.

[Authorize(Roles = "Admin, Editor")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

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

If I only wanted the Admin role to have access to the About action, I could do the following:

[Authorize(Roles = "Admin, Editor")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles = "Admin")] // this will take precedence over the controller's authorization
    public ActionResult About()
    {
        return View();
    }
}

Is there a way to accomplish this without listing every single role that needs access, and only specifying the roles that should be prevented from having access?


Solution

  • Here is the code for the class I used to solve this problem. It derives heavily from AuthorizeAttribute, and will allow any authenticated user through who does not match the specifications set by the parameters.

    (Note that the important method is AuthorizeCore - everything else is essentially copied or inherited from AuthorizeAttribute)

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
    public class BlackListAttribute : AuthorizeAttribute
    {
        private static readonly string[] _emptyArray = new string[0];
    
        private string _roles;
        private string _users;
    
        private string[] _rolesSplit = _emptyArray;
        private string[] _usersSplit = _emptyArray;
    
        public new string Roles
        {
            get { return _roles ?? String.Empty; }
            set
            {
                _roles = value;
                _rolesSplit = SplitString(value);
            }
        }
    
        public new string Users
        {
            get { return _users ?? String.Empty; }
            set
            {
                _users = value;
                _usersSplit = SplitString(value);
            }
        }
        // This is the important part. Everything else is either inherited from AuthorizeAttribute or, in the case of private or internal members, copied from AuthorizeAttribute.
        protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
    
            IPrincipal user = httpContext.User;
    
            if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
            {
                return false;
            }
    
            if (_usersSplit.Length > 0 && _usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
            {
                return false;
            }
    
            if (_rolesSplit.Length > 0 && _rolesSplit.Any(user.IsInRole))
            {
                return false;
            }
    
            return true;
        }
    
        internal static string[] SplitString(string original)
        {
            if (String.IsNullOrEmpty(original))
            {
                return _emptyArray;
            }
    
            var split = from piece in original.Split(',')
                        let trimmed = piece.Trim()
                        where !String.IsNullOrEmpty(trimmed)
                        select trimmed;
            return split.ToArray();
        }
    }
    

    You can use it on controllers or actions like any other AuthorizeAttribute:

    [Authorize(Roles = "Admin, Editor")]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        [BlackList(Roles = "Editor")]
        public ActionResult About()
        {
            return View();
        }
    }