In my continual quest for find better ways of doing anything and everything this week I decided to tackle on of my arch nemesis: Security in ASP.Net, specifically Security in ASP.Net MVC3. You might ask why? We all know that there are at least 2 easy ways to implement security in MVC3
- Authorize Attributes on Controllers and Actions
- web.config locations
Yuck! I hate both of them for the same reason: they become very difficult to manage. Authorize Attributes end up spread all over your application and web.config location elements require 7 lines of xml for 1 rule. So I went searching and came across a NuGet package called FluentSecurity.
The documentation covered all the examples I needed and you can define rules in only 1 line of code!
configuration.For<AccountController>.RequireRole("Administrator", "Legal");
I ran into 2 difficulties when trying to implement this:
- Implementing an Access Denied page and re-direct to Login
- Integrating FluentSecurity with MvcSiteMapProvider
If you want to skip all the work and get straight to a working example download a sample application here FluentSecurityAndMvcSiteMapProvider.zip
Implementing an Access Denied page and re-direct to Login
The current NuGet version (1.4 as at 05/2012) required that you use StructureMap or another IoC container to implement a custom IPolicyViolationHandler. I wasn’t using either but discovered that it is not a requirement in version 2 which is in alpha at the time of writing but works fine for me. So make sure either the Version you get from NuGet is at least 2.0 or got to GitHub to get the latest version.
First we need to create a class implementing IPolicyViolationHandler.
The class that we create will:
- Send unauthenticated users to a shared view called AccessDenied
- Redirect authenticated users without access to the current controller to the Account controllers LogIn action
using System.Web.Mvc;
using System.Web.Routing;
using FluentSecurity;
public class DefaultPolicyViolationHandler : IPolicyViolationHandler
{
public string ViewName = "AccessDenied";
public ActionResult Handle(PolicyViolationException exception)
{
if (SecurityHelper.UserIsAuthenticated())
{
return new ViewResult { ViewName = ViewName };
}
else
{
RouteValueDictionary rvd = new RouteValueDictionary();
if (System.Web.HttpContext.Current.Request.RawUrl != "/")
rvd["ReturnUrl"] = System.Web.HttpContext.Current.Request.RawUrl;
rvd["controller"] = "Account";
rvd["action"] = "LogOn";
rvd["area"] = "";
return new RedirectToRouteResult(rvd);
}
}
}
Now we need to set / tell FluentSecurity to use our DefaultPolicyViolationHandler. For this I created a SecurityHelper class.
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FluentSecurity;
using FluentSecurityAndMvcSiteMapProvider.Areas.Admin.Controllers;
using FluentSecurityAndMvcSiteMapProvider.Controllers;
public static class SecurityHelper
{
public static ISecurityConfiguration SetupFluentSecurity()
{
SecurityConfigurator.Configure(configuration =>
{
// Let Fluent Security know how to get the authentication status of the current user
configuration.GetAuthenticationStatusFrom(SecurityHelper.UserIsAuthenticated);
configuration.GetRolesFrom(SecurityHelper.UserRoles);
// It is reccommended not to use this setting. For all of our applications users must always be authenticated.
configuration.IgnoreMissingConfiguration();
configuration.DefaultPolicyViolationHandlerIs(() => new DefaultPolicyViolationHandler());
//Make sure user must be authenticated but allow unauthenticated access to the logon screen
configuration.ForAllControllers().DenyAnonymousAccess();
configuration.For<AccountController>(ac => ac.LogOn()).Ignore();
//first deny access to all users except Administrators
configuration.ForAllControllersInNamespaceContainingType<AdminController>()
.DenyAuthenticatedAccess()
.RequireRole(RolesEnum.AdminRole.ToString());
//If any users have access to part of the admin console they will access to the admin dashboard
configuration.For<AdminController>().RequireRole(RolesEnum.AdminRole.ToString(), RolesEnum.LegalRole.ToString());
//grant access to any other controllers in the admin area
configuration.For<LegalController>().RequireRole(RolesEnum.AdminRole.ToString(), RolesEnum.LegalRole.ToString());
});
return SecurityConfiguration.Current;
}
public static bool UserIsAuthenticated()
{
var currentUser = HttpContext.Current.User;
return !string.IsNullOrEmpty(currentUser.Identity.Name);
}
public static IEnumerable<object> UserRoles()
{
var currentUser = HttpContext.Current.User;
return string.IsNullOrEmpty(currentUser.Identity.Name) ? null : System.Web.Security.Roles.GetRolesForUser(currentUser.Identity.Name);
}
}
Finally configure our system to use FluentSecurity in Global.asax.cs
using System.Web.Mvc;
using System.Web.Routing;
using FluentSecurity;
namespace FluentSecurityAndMvcSiteMapProvider
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
//required for FluentSecurity
filters.Add(new HandleSecurityAttribute(), 0);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
//Configure FluentSecurity
SecurityHelper.SetupFluentSecurity();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
}
Integrating FluentSecurity with MvcSiteMapProvider
With security working I needed to filter my site map so unauthenticated users can’t see menu items they shouldn’t. Becase we love to user MvcSiteMapProvider I could have easily just added a role attribute to each mvcSiteMapNode but then we are doubling up on security definitions and I just can’t have that.
To hook into MvcSiteMapProvier and use our own Visibility provider we needed to do a few things
- Create a new function in the SecurityHelper to check if a user has access to a particular action
- Create a new class implementing ISiteMapNodeVisibilityProvider
Creating ActionIsAllowedForUser. The one thing that is important to note about this function is that GetContainerFor asks for a parameter named controllerName. This is actually after the controllerNamespace.
public static class SecurityHelper
{
public static ISecurityConfiguration SetupFluentSecurity()
{
//...
}
public static bool UserIsAuthenticated()
{
//...
}
public static IEnumerable<object> UserRoles()
{
//...
}
public static bool ActionIsAllowedForUser(string controllerNamespace, string actionName)
{
var configuration = SecurityConfiguration.Current;
var policyContainer = configuration.PolicyContainers.GetContainerFor(controllerNamespace, actionName);
if (policyContainer != null)
{
var context = SecurityContext.Current;
var results = policyContainer.EnforcePolicies(context);
return results.All(x => x.ViolationOccured == false);
}
return true;
}
Now we can implement a SiteMapNodeVisibilityProvider to call ActionIsAllowedForUser. Lucky MvcSiteMapProvider provides us with this wonderful function that will get the full controller namespace for us!
using System.Collections.Generic;
using System.Web;
using MvcSiteMapProvider;
using MvcSiteMapProvider.Extensibility;
public class ReviumVisibilityProvider : ISiteMapNodeVisibilityProvider
{
public bool IsVisible(SiteMapNode node, HttpContext context, IDictionary sourceMetadata)
{
// Convert to MvcSiteMapNode
var mvcNode = node as MvcSiteMapNode;
if (mvcNode == null)
{
return true;
}
//First check the visibility based on user roles
string controllerNamespace = (new DefaultControllerTypeResolver()).ResolveControllerType(mvcNode.Area, mvcNode.Controller).FullName;
bool isVisible = SecurityHelper.ActionIsAllowedForUser(controllerNamespace, mvcNode.Action);
if (!isVisible)
return false;
//Process Other Visibility Rules
//...
return true;
}
}
And that’s it! We now have a site that full implements FluentSecurity for validation, displays the appropriate Access denied or login page and displays the correct site map foe each user.
Checkout a working version here FluentSecurityAndMvcSiteMapProvider.zip
Related posts:
- Creating a generic settings repository in C#
- Asp.Net MVC convert View to Word Document
- Modified properties in LINQ to SQL
- Sharepoint – Getting Photos from People and Groups
- ASP.NET MVC3 – Application_Error not firing log4net
Tags: asp.net, c#, FluentSecurity, MVC3, MvcSiteMapProvider

Great post.
However I have one question. I downloaded the example and implemented the same code into my solution. There is a method missing:
DefaultPolicyViolationHandlerIs
I have downloaded the same version of FluentSecurity and MvcSiteMapprovider from NuGet.
If you used nuget you need to make sure you use 2.0.0-alpha1 for this example
http://nuget.org/packages/FluentSecurity/2.0.0-alpha1
Great article ! It meets all my requirement ,execellent!
just I could not find the last class file of “ReviumVisibilityProvider ” ?why
I get the file from the link you provided in the middle of the article ”
FluentSecurityAndMvcSiteMapProvider.zip “
I have a requirement where roles are not fixed, it is defined in DB. A role is nothing but a set of action to which the user has access to. In MVC terms action is the controller action.
A action should be fetched from DB and checked when a user request a controller action. Which means it does not matter which role I belong, I should always check if the user has access to perform that action.
Say for example AddEmployee(), UpdateEmployee() are two action in AdminController. A custom role can be created which have AddEmployee and UpdateEmployee rights. Again a custom role can be created which has access to only AddEmployee.
How can we implement this in Fluent Security
@Numen: ReviumVisibilityProvider can be found in the MvcSiteMapHelper.cs file
@Sumit
You could probably do it by enhancing SecurityHelper.SetupFluentSecurity().
Without trying to get into reflection you could just define a configuration.For for each action and get the required roles from the database
string[] roles = GetRolesForAction(“AddEmployee”);().RequireRole(roles);
configuration.For
Update SecurityHelper.UserRoles to return your dynamic role names for the current user
return GetFolesForUser(HttpContext.Current.User.Identity.Name);
And you should be fine. Each time you create a new role you would need to call SecurityHelper.SetupFluentSecurity(); again. I’m not sure how that will work though.
Thanks. Let me try this and will let you know hows that works. Thanks
I have requirement in which i have apply authorization on control. For e.g. button and link should be disable as per the user role and action.
FluentSecurity is really aimed at securing routes and not individual elements in a view. I perform those security checks separately.