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.