Authorization in ASP.Net MVC using XML Configuration.
Doing authorization in a clean way is always tricky, You want a delicate balance between an extreme abstraction and something like embedding roles in-side your compiled code, I have always preferred simple abstraction either using roles and their corresponding mappings in the database or using simple xml file to store action to role mappings.
Asp.net MVC comes with built in Authorization filter attribute that you can use on your Controller and Action to define the role that can access corresponding Controller or Action. This approach will work fine for small application where you have predefined action to role mappings, but when you have bigger application where developers are not going to define role mappings and mappings might change frequently then maintenance of hard quoted roles might become nightmare.
I have created simple application to demonstrate how you can xml based configuration to apply authorization in MVC application. Following are four major pieces of the application.
- ConfigurationSectionHandler for defining XML Configuration for Controller and Action to role mapping.
- IMVCAuthorizer Interface and implementation MCVXMLAuthorizer.
- HttpModule which plugs into AuthorizeRequest event to validate if user are authorized to access Controller and Action.
- Sample MVC application to test XMLAuthorizer.
Defining XML and Configuration Section.
Following is the XML structure that defines Controller and Action to
role mappings, As you can see Controller and Actions can have their own
set of roles. Empty Home Controller section means everybody has access
to Controller and all Actions.
As you can also see in first controller node Admin, Edit and View roles have access to Admin Controller but access to Edit and Admin action if limited to specific roles only.
< controller name ="Admin">
< roles >
< role > Admin </ role >
< role > Edit </ role >
< role > View </ role >
</ roles >
< actions >
< action name ="Index">
< roles >
< role > Admin </ role >
< role > Edit </ role >
< role > View </ role >
</ roles >
</ action >
< action name ="Edit">
< roles >
< role > Edit </ role >
< role > Admin </ role >
</ roles >
</ action >
< action name ="Admin">
< roles >
< role > Admin </ role >
</ roles >
</ action >
</ actions >
</ controller >
< controller name ="Home"> </ controller >
</ controllers >
Configuration Section handler has two important properties 1) Type which defines fully qualified type name of class implements IXMLAuthorizer interface and 2) ConnectionString which in our case is the path of XMLConfiguration file, additionally It also has static GetSettingsMethod which returns current settings from Web.Config file.
public object Create(object parent, object configContext, System.Xml.XmlNode section) {
// Create an instance of XmlSerializer based on the RewriterConfiguration type...
XmlSerializer ser = new XmlSerializer(typeof(AuthorizationMappingSection));
// Return the Deserialized object from the Web.config XML
return ser.Deserialize(new XmlNodeReader(section));
}
public static AuthorizationMappingSection GetSettings() {
if (HttpContext.Current.Cache["AuthorizationMappingSection"] == null) {
AuthorizationMappingSection settings
= (AuthorizationMappingSection)ConfigurationManager.GetSection("AuthorizationMappingSection");
HttpContext.Current.Cache["AuthorizationMappingSection"] = settings;
}
return ((AuthorizationMappingSection)HttpContext.Current.Cache["AuthorizationMappingSection"]);
}
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("connectionString")]
public string ConnectionString { get; set; }
}
IMVCAuthorizer and MCVXMLAuthorizer Implementation.
IMVCAuthorizer interface has two methods, 1) IsAuthorized which takes controllerName and actionName and Principle object. And 2) Initialize methods which takes connection string.public interface IMVCAuthorizer {
bool IsAuthorized(string controllerName, string actionName, System.Security.Principal.IPrincipal user);
void Initilize(string connectionString);
}
MCVXMLAuthorizer implements IMVCAuthorizer, In Initialize method it reads XML file and de-serialize it into ControllerAuthorizationInfoCollection class, as you can see it puts de-serialize object into cache and puts dependency on physical file so any time some body changes the file cache will be invalidated.
IsAuthorized is the method that contains logic to check authorization against XML configuration you can download the source code if you want to go deeper into logic inside ControllerAuthorizationInfoCollection class.
ControllerAuthorizationInfoCollection controllers = null;
public bool IsAuthorized(string controllerName ,string actionName, System.Security.Principal.IPrincipal user) {
return controllers.CanAccessAction(controllerName,actionName, user);
}
public void Initilize(string connectionString) {
string key = "MCVXMLAuthorizerCacheKey";
if (HttpContext.Current.Cache[key] != null) {
controllers = (ControllerAuthorizationInfoCollection)HttpContext.Current.Cache[key];
} else {
string path = HttpContext.Current.Server.MapPath(connectionString);
controllers = getControllerAuthorizationInfoCollection(path);
HttpContext.Current.Cache.Insert(key, controllers, new System.Web.Caching.CacheDependency(path));
}
}
private ControllerAuthorizationInfoCollection getControllerAuthorizationInfoCollection(string path) {
ControllerAuthorizationInfoCollection rVal = null;
XmlSerializer ser = new XmlSerializer(typeof(ControllerAuthorizationInfoCollection));
using (FileStream fs = File.OpenRead(path)) {
rVal = (ControllerAuthorizationInfoCollection)ser.Deserialize(fs);
}
return rVal;
}
}
HttpModule
AuthorizationMappingModule is an HttpModule which hooks into AuthorizedRequest event of page life cycle and check is user has access to particular controller and action, if user does not have privilege to access then it will throw Security Exception.HttpContext context = ((HttpApplication)sender).Context;
RouteData routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
if (routeData != null) {
string controller = routeData.GetRequiredString("controller");
string action = routeData.GetRequiredString("action");
IMVCAuthorizer authorizer = GetMVCAuthorizer();
if (!authorizer.IsAuthorized(controller, action, context.User)) {
string message = string.Format("User {0} does not have permission to access {1} on {2}"
, context.User.Identity.Name, action, controller);
System.Diagnostics.Trace.TraceInformation(message);
throw new SecurityException(message);
}
}
}
Sample MVC Application to test Authorization.
I have created a simple MVC application with two Controllers, Controller that we are going to test is Admin Controller, I have also simple UI in Home Controller which allows you to login as an different user and try to access different links. As an-authenticated user you should not have access to any actions on Admin Controller and as you switch your role by clicking different login links your permission will change. following is the screen shot of application.You will need to add reference to MVCAuthorization.dll and following configuration section handler and http module settings in your web.config file.