User authentication against AD and Roles based authorization in ASP.NET MVC – Part II
In this post we are going to focus on second option when the roles are maintained in the DB.
Step 1: In the web.config file, we have to specify the database connection string where the roles are stored:
<connectionStrings>
<add name="ApplicationServices" connectionString="data source=.;Integrated Security=SSPI; Initial Catalog=myroledb" providerName="System.Data.SqlClient"/>
</connectionStrings>
Then we have to enable Windows Authentication like below:
<authentication mode="Windows">
</authentication>
Then we have to specify the CustomRoleProvider like below:
<roleManager
defaultProvider="CustomRoleProvider" enabled="true" cacheRolesInCookie="true" cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All">
<providers>
<clear/>
<add name="CustomRoleProvider" type="MyApplication.Web.Providers.CustomRoleProvider" connectionStringName="ApplicationServices" writeExceptionsToEventLog="false"/>
</providers>
</roleManager>
Then we have to specify allowed roles using Authorization tag like below:
<authorization>
<allow roles="Administrator, Manager"/>
<deny users="?"/>
</authorization>
Step 2:
Create the Role Provider :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Configuration.Provider;
using System.Collections.Specialized;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
namespace MyApplication.Web.Providers
{
public sealed class CustomRoleProvider : RoleProvider
{
//
// Global connection string, generic exception message, event log info.
//
private string eventSource = "CustomRoleProvider";
private string eventLog = "Application";
private string exceptionMessage = "An exception occurred. Please check the Event Log.";
private ConnectionStringSettings pConnectionStringSettings;
private string connectionString;
//
// If false, exceptions are thrown to the caller. If true,
// exceptions are written to the event log.
//
private bool pWriteExceptionsToEventLog = false;
public bool WriteExceptionsToEventLog
{
get { return pWriteExceptionsToEventLog; }
set { pWriteExceptionsToEventLog = value; }
}
//
// System.Configuration.Provider.ProviderBase.Initialize Method
//
public override void Initialize(string name, NameValueCollection config)
{
//
// Initialize values from web.config.
//
if (config == null)
throw new ArgumentNullException("config");
if (name == null || name.Length == 0)
name = "CustomRoleProvider";
if (String.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "Custom Role provider");
}
// Initialize the abstract base class.
base.Initialize(name, config);
if (config["writeExceptionsToEventLog"] != null)
{
if (config["writeExceptionsToEventLog"].ToUpper() == "TRUE")
{
pWriteExceptionsToEventLog = true;
}
}
//
// Initialize SQLConnection.
//
pConnectionStringSettings = ConfigurationManager.ConnectionStrings["ApplicationServices"];
if (pConnectionStringSettings == null || pConnectionStringSettings.ConnectionString.Trim() == "")
{
throw new ProviderException("Role Provider Connection string cannot be blank. Please check the DB conenction");
}
connectionString = pConnectionStringSettings.ConnectionString;
}
//
// System.Web.Security.RoleProvider properties.
//
private string pApplicationName;
public override string ApplicationName
{
get { return pApplicationName; }
set { pApplicationName = value; }
}
//
// RoleProvider.GetAllRoles
//
public override string[] GetAllRoles()
{
string tmpRoleNames = "";
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("SP_GET_AllRoleNamesFromDB", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader reader = null;
try
{
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
tmpRoleNames += reader.GetString(2) + ",";
}
}
catch (SqlException e)
{
if (WriteExceptionsToEventLog)
{
logger.LogException(e);
}
else
{
throw e;
}
}
finally
{
if (reader != null) { reader.Close(); }
conn.Close();
}
if (tmpRoleNames.Length > 0)
{
// Remove trailing comma.
tmpRoleNames = tmpRoleNames.Substring(0, tmpRoleNames.Length - 1);
return tmpRoleNames.Split(',');
}
return new string[0];
}
//
// RoleProvider.GetRolesForUser
//
public override string[] GetRolesForUser(string username)
{
string tmpRoleNames = "";
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("SP_GetRolesforUser", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@Username", SqlDbType.VarChar, 255).Value = username;
SqlDataReader reader = null;
try
{
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
tmpRoleNames += reader.GetString(0) + ",";
}
}
catch (SqlException e)
{
if (WriteExceptionsToEventLog)
{
logger.LogException(e);
}
else
{
throw e;
}
}
finally
{
if (reader != null) { reader.Close(); }
conn.Close();
}
if (tmpRoleNames.Length > 0)
{
// Remove trailing comma.
tmpRoleNames = tmpRoleNames.Substring(0, tmpRoleNames.Length - 1);
return tmpRoleNames.Split(',');
}
return new string[0];
}
//
// RoleProvider.GetUsersInRole
//
public override string[] GetUsersInRole(string rolename)
{
string tmpUserNames = "";
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("SP_GetUsersInRole", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@Rolename", SqlDbType.VarChar, 255).Value = rolename;
SqlDataReader reader = null;
try
{
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
tmpUserNames += reader.GetString(0) + ",";
}
}
catch (SqlException e)
{
if (WriteExceptionsToEventLog)
{
logger.LogException(e);
}
else
{
throw e;
}
}
finally
{
if (reader != null) { reader.Close(); }
conn.Close();
}
if (tmpUserNames.Length > 0)
{
// Remove trailing comma.
tmpUserNames = tmpUserNames.Substring(0, tmpUserNames.Length - 1);
return tmpUserNames.Split(',');
}
return new string[0];
}
//
// RoleProvider.IsUserInRole
//
public override bool IsUserInRole(string username, string rolename)
{
bool userIsInRole = false;
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("SP_IsUserInRole", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@Username", SqlDbType.VarChar, 255).Value = username;
cmd.Parameters.Add("@Rolename", SqlDbType.VarChar, 255).Value = rolename;
try
{
conn.Open();
int numRecs = (int)cmd.ExecuteScalar();
if (numRecs > 0)
{
userIsInRole = true;
}
}
catch (SqlException e)
{
if (WriteExceptionsToEventLog)
{
logger.LogException(e);
}
else
{
throw e;
}
}
finally
{
conn.Close();
}
return userIsInRole;
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new NotImplementedException();
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
}
}
Step 3:
Create the Custom Authorize Attribute
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MyApplication.Web.Attributes
{
/// <summary>
/// Attribute which handled unauthorized request redirection
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorize : AuthorizeAttribute
{
private bool _isAuthorized;
/// <summary>
/// Caches the validate handler.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="data">The data.</param>
/// <param name="validationStatus">The validation status.</param>
protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
/// <summary>
/// Authorizes the core.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns></returns>
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
_isAuthorized = httpContext.User.Identity.IsAuthenticated;
return _isAuthorized;
}
/// <summary>
/// Called when [authorization].
/// </summary>
/// <param name="filterContext">The filter context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!_isAuthorized)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("Manager"))
{
// is authenticated and is in one of the roles
SetCachePolicy(filterContext);
}
else
{
if (!filterContext.Controller.TempData.ContainsKey("RedirectReason"))
{
filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
}
filterContext.Result = new RedirectResult("~/Error");
}
}
/// <summary>
/// Sets the cache policy.
/// </summary>
/// <param name="filterContext">The filter context.</param>
protected void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
}
}
Step 4: On the Error page – you can access the TempDate[“RedirectReason”] to show the unauthorized access message.
Step 5: On all the actions which requires Role specific access use following attribute:
[CustomAuthorize (Roles=”Administrator,Manager”)]
With this we have covered both the cases. I understand the approach/code mentioned in these two posts are very limited and covers very specific scenarios. Feel free to add /modify the code provided as per your need.
Comments
Anonymous
June 21, 2010
I would like to try out your solution, but I am getting the following error: "Error 1 The name 'logger' does not exist in the current context" Am I missing something?Anonymous
June 21, 2010
Sorry for the miss. I have implemented EntLib Exception Handling & Logging block in my application. 'logger' is an object of that class. You can use your own way to log the exception. If you comment all the lines where you are referring logger, the code should work. let me know.