Here is what I came up with, in the end, as a filter, I add to the collection of global filters for the MVC application:
/// <summary> /// This filter should be applied to an MVC application as a global filter in RegisterGlobalFilters, not applied to individual actions/controllers. /// It will cause access to every action to be DENIED by default. /// If an AllowAnonymousAttribute is applied, all authorization checking is skipped (this takes precedence over AuthorizeSafeAttribute). /// If an AuthorizeSafeAttribute is applied, only the roles specified in AuthorizeSafeAttribute Roles property will be allowed access. /// </summary> public sealed class AuthorizeSafeFilter : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { if (!string.IsNullOrEmpty(this.Roles) || !string.IsNullOrEmpty(this.Users)) { throw new Exception("This class is intended to be applied to an MVC application as a global filter in RegisterGlobalFilters, not applied to individual actions/controllers. Use the AuthorizeSafeAttribute with individual actions/controllers."); } // Disable caching for this request filterContext.HttpContext.Response.Cache.SetNoServerCaching(); filterContext.HttpContext.Response.Cache.SetNoStore(); // If AllowAnonymousAttribute applied, skip authorization if ( filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ) { return; } // Backup original roles string rolesBackup = this.Roles; // Look for AuthorizeSafeAttribute roles bool foundRoles = false; string foundRolesString = null; object[] actionCustomAttributes = filterContext.ActionDescriptor.GetCustomAttributes(false); object[] controllerCustomAttributes = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(false); if (actionCustomAttributes.Any(attr => attr is AuthorizeSafeAttribute)) { AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(actionCustomAttributes.First(attr => attr is AuthorizeSafeAttribute)); foundRoles = true; foundRolesString = foundAttr.Roles; } else if (controllerCustomAttributes.Any(attr => attr is AuthorizeSafeAttribute)) { AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(controllerCustomAttributes.First(attr => attr is AuthorizeSafeAttribute)); foundRoles = true; foundRolesString = foundAttr.Roles; } if (foundRoles && !string.IsNullOrWhiteSpace(foundRolesString)) { // Found valid roles string; use it as our own Roles property and auth normally this.Roles = foundRolesString; base.OnAuthorization(filterContext); } else { // Didn't find valid roles string; DENY all access by default filterContext.Result = new HttpUnauthorizedResult(); } // Restore original roles this.Roles = rolesBackup; } }
I also define this attribute:
I apply AllowAnonymousAttribute to my actions / login controllers and AuthorizeSafeAttribute to others, but if I forget to apply them, access will be denied by default. I want ASP.NET MVC to be as safe as this by default. :-)
source share