I really liked the code from @Robert's post, but there were a few bugs and I wanted to cache the collection of roles and users because reflection can be a little expensive.
Bugs fixed: if there is both the Controller attribute and the Action attribute, then when the roles become concatenated, the extra comma does not appear between the controller roles and the action roles that will not be correctly analyzed.
[Authorize(Roles = "SuperAdmin,Executives")] public class SomeController() { [Authorize(Roles = "Accounting")] public ActionResult Stuff() { } }
then the line of roles ends with SuperAdmin,ExecutivesAccounting , my version ensures that Executives and Accounting are separate.
My new code also ignores Auth for HttpPost actions, because it can drop things, although it is unlikely.
Finally, it returns MvcHtmlString instead of string for newer versions of MVC
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Reflection; using System.Collections; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Security.Principal; public static class HtmlHelperExtensions { /// <summary> /// only show links the user has access to /// </summary> /// <returns></returns> public static MvcHtmlString SecurityLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled = false) { if (IsAccessibleToUser(action, controller)) { return htmlHelper.ActionLink(linkText, action, controller); } else { return new MvcHtmlString(showDisabled ? String.Format("<span>{0}</span>", linkText) : ""); } } /// <summary> /// reflection can be kinda slow, lets cache auth info /// </summary> private static Dictionary<string, Tuple<string[], string[]>> _controllerAndActionToRolesAndUsers = new Dictionary<string, Tuple<string[], string[]>>(); private static Tuple<string[], string[]> GetAuthRolesAndUsers(string actionName, string controllerName) { var controllerAndAction = controllerName + "~~" + actionName; if (_controllerAndActionToRolesAndUsers.ContainsKey(controllerAndAction)) return _controllerAndActionToRolesAndUsers[controllerAndAction]; Type controllerType = GetControllerType(controllerName); MethodInfo matchingMethodInfo = null; foreach (MethodInfo method in controllerType.GetMethods()) { if (method.GetCustomAttributes(typeof(HttpPostAttribute), true).Any()) continue; if (method.GetCustomAttributes(typeof(HttpPutAttribute), true).Any()) continue; if (method.GetCustomAttributes(typeof(HttpDeleteAttribute), true).Any()) continue; var actionNameAttr = method.GetCustomAttributes(typeof(ActionNameAttribute), true).Cast<ActionNameAttribute>().FirstOrDefault(); if ((actionNameAttr == null && method.Name == actionName) || (actionNameAttr != null && actionNameAttr.Name == actionName)) { matchingMethodInfo = method; } } if (matchingMethodInfo == null) return new Tuple<string[], string[]>(new string[0], new string[0]); var authAttrs = new List<AuthorizeAttribute>(); authAttrs.AddRange(controllerType.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>()); var roles = new List<string>(); var users = new List<string>(); foreach(var authAttr in authAttrs) { roles.AddRange(authAttr.Roles.Split(',')); users.AddRange(authAttr.Roles.Split(',')); } var rolesAndUsers = new Tuple<string[], string[]>(roles.ToArray(), users.ToArray()); try { _controllerAndActionToRolesAndUsers.Add(controllerAndAction, rolesAndUsers); } catch (System.ArgumentException ex) { //possible but unlikely that two threads hit this code at the exact same time and enter a race condition //instead of using a mutex, we'll just swallow the exception when the method gets added to dictionary //for the second time. mutex only allow single worker regardless of which action method they're getting //auth for. doing it this way eliminates permanent bottleneck in favor of a once in a bluemoon time hit } return rolesAndUsers; } public static bool IsAccessibleToUser(string actionName, string controllerName) { var rolesAndUsers = GetAuthRolesAndUsers(actionName, controllerName); var roles = rolesAndUsers.Item1; var users = rolesAndUsers.Item2; IPrincipal principal = HttpContext.Current.User; if (!roles.Any() && !users.Any() && principal.Identity.IsAuthenticated) return true; foreach (string role in roles) { if (role == "*" || principal.IsInRole(role)) return true; } foreach (string user in users) { if (user == "*" && (principal.Identity.Name == user)) return true; } return false; } public static Type GetControllerType(string controllerName) { Assembly assembly = Assembly.GetExecutingAssembly(); foreach (Type type in assembly.GetTypes()) { if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper()))) { return type; } } return null; } }