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.