Thursday, June 18, 2009

Asp.Net Forms Authentication with Groups and Roles

I found this great post from Rob Convery on creating a FilterAttribute that can be used to authorize specific controller actions against roles. I’d always wanted to implement a Group strategy and had made several attempts. They all seemed like kluges and I was never really happy.

One small change to Rob’s code and I can do grouping in a simple manner. Note: If you have a large number of roles and groups, this will probably not work for you. Also, this uses Asp.Net MVC.

ok, Rob’s code basically checked for a single role and you could use a constant to supply the role name. Something like

[RequiresRoleAttribute(RoleToCheckFor = ApplicationRoles.AdminRole)]
public ActionResult Edit(int id)

My change just allows you to pass more than one role into RoleToCheckFor.  Basically, I split RoleToCheckFor using a comma.

[RequiresRoleAttribute(RoleToCheckFor = ApplicationRoles.ChangeClientsGroup)]
public ActionResult Edit(int id)
The ApplicationRoles class looks like this.
public class ApplicationRoles
{
public const string AdminRole = "Admin";
public const string EntryRole = "Entry";
public const string ManagementRole = "Management";
public const string SecurityRole = "Security";

public const string ChangeClientsGroup = Admin + "," + Entry;
}

ChangeClientsGroup is simply a constant that contains all the allowed roles for changing clients. Biggest drawback here, I want to change a group, I need to redeploy. This isn’t really a problem since I don’t have to change my roles a lot but…
public class RequiresRoleAttribute : ActionFilterAttribute
{
public string RoleToCheckFor { get; set; }

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//redirect if the user is not authenticated
if (!String.IsNullOrEmpty(RoleToCheckFor))
{

if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{

//use the current url for the redirect
string redirectOnSuccess = filterContext.HttpContext.Request.Url.AbsolutePath;

//send them off to the login page
string redirectUrl = string.Format("?ReturnUrl={0}", redirectOnSuccess);
string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
filterContext.HttpContext.Response.Redirect(loginUrl, true);

}
else
{
bool isAuthorized = false;
string[] roles = this.RoleToCheckFor.Replace(" ", string.Empty).Split(',');
foreach (string role in roles)
{
isAuthorized = filterContext.HttpContext.User.IsInRole(role);
if (isAuthorized) { break; }
}
if (!isAuthorized)
{
ApplicationError appEevent = new ApplicationError();
appEevent.Caller = Thread.CurrentPrincipal.Identity.Name + ":" + filterContext.Controller.GetType().Name + ":" + filterContext.HttpContext.Request.Path;
appEevent.ErrorMessage = "Unauthorized access attempt";
ApplicationManager.RecordEvent(appEevent);
FormsAuthentication.SignOut();
filterContext.HttpContext.Response.Redirect("/SecurityViolation.htm", true);
}
}
}
else
{
throw new InvalidOperationException("No Role Specified");
}
}
}

Hopes this works for you!