ASP.NET MVC has good support for role-based security, but the usage of strings as role names is maddening, simply because they cannot be strongly-typed as enumerations.
For example, I have an "Admin" role in my app. The "Admin" string will now exist in the Authorize attribute of my action, in my master page (for hiding a tab), in my database (for defining the roles available to each user), and any other place in my code or view files where I need to perform special logic for admin or non-admin users.
Is there a better solution, short of writing my own authorization attribute and filter, that would perhaps deal with a collection of enumeration values?
Using magic strings gives you the flexibility to declare multiple roles in the Authorize attribute (e.g. [Authorize(Roles = "Admin, Moderator")] which you tend to lose as you go to a strongly typed solution. But here's how you can maintain this flexibility while still getting everything strongly typed.
Define your roles in an enum that uses bit flags:
[Flags]
public enum AppRole {
Admin = 1,
Moderator = 2,
Editor = 4,
Contributor = 8,
User = 16
}
Override AuthorizeAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute {
public AppRole AppRole { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext) {
if (AppRole != 0)
Roles = AppRole.ToString();
base.OnAuthorization(filterContext);
}
}
Now if you can use MyAuthorizeAttribute like this:
[MyAuthorize(AppRole = AppRole.Admin | AppRole.Moderator | AppRole.Editor)]
public ActionResult Index() {
return View();
}
The above action will only authorize users that are in at least one of the roles listed (Admin, Moderator, or Editor). The behavior is the same as MVC's default AuthorizeAttribute, except without the magic strings.
If you use this technique, here's an extension method on IPrincipal that may also be useful:
public static class PrincipalExtensions {
public static bool IsInRole(this IPrincipal user, AppRole appRole) {
var roles = appRole.ToString().Split(',').Select(x => x.Trim());
foreach (var role in roles) {
if (user.IsInRole(role))
return true;
}
return false;
}
}
You can use this extension method like this:
public ActionResult Index() {
var allowed = User.IsInRole(AppRole.Admin | AppRole.Moderator | AppRole.Editor);
if (!allowed) {
// Do Something
}
return View();
}