Skip to content
Shad Storhaug edited this page Feb 21, 2014 · 1 revision

Security trimming is the ability of MvcSiteMapProvider to change the visibility of nodes according to an external security scheme. MvcSiteMapProvider does not actually provide security, it simply shows or hides nodes according to an external security context.

The MVC controller actions, pages, or other resources that those nodes represent must be secured using the built-in features of MVC, ASP.NET, and/or IIS otherwise the URLs will still be available even if the nodes that represent them are not visible.

Enabling Security Trimming

By default, security trimming is disabled. To use it, the first thing you need to do is turn it on.

Internal DI (web.config)

<appSettings>
    <add key="MvcSiteMapProvider_SecurityTrimmingEnabled" value="true"/>
</appSettings>

External DI (MvcSiteMapProvider Module)

bool securityTrimmingEnabled = true; // Near the top of the module

This parameter is passed into the constructor of the SiteMapBuilderSet class.

ASP.NET MVC Security Primer

The recommended way to configure security in MVC is to use the AuthorizeAttribute, or a class that inherits AuthorizeAttribute.

The AuthorizeAttribute can be applied to controllers and actions to specify what roles (or users) have access to the resource (controller or action). If you don't specify a user or role on the AuthorizeAttribute, then all logged in users will have access.

It is usually best if you register AuthorizeAttribute in your global filters, which will make all of your pages require a logged on user by default. Then you can decorate only the actions you want unauthenticated users to access with the AllowAnonymousAttribute. If you do it this way, you enable "white list" security. That is, everything is secure except for what you specifically "white list" using the AllowAnonymousAttribute.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
		// By default, all actions require a logged in user
        filters.Add(new AuthorizeAttribute());
    }
}

public class HomeController : Controller
{
	// All users have access
	[AllowAnonymous]
	public ActionResult Index()
	{
		return View();
	}
	
	// Only logged in users have access
	public ActionResult ViewSomethingPrivate()
	{
		return View();
	}
}

// Only users in the "Manager" role have access to all actions on this controller
[Authorize(Roles = "Manager")]
public class AccountController : Controller
{
	public ActionResult Manage()
	{
		return View();
	}
	
	public ActionResult ChangePassword()
	{
		return View();
	}
}

Once you have your security settings in place and have enabled security trimming, MvcSiteMapProvider will automatically hide the nodes the current user doesn't have access to.

Do note, however, that a child node cannot have lower restrictions than a parent node in the hierarchy. This is usually not a problem because we typically want to hide all of the descendants of a node that the user doesn't have access to. However, this is something that MVC allows that MvcSiteMapProvider does not.

Subclassing AuthorizeAttribute

MvcSiteMapProvider will automatically take into account classes that inherit AuthorizeAttribute if you need to customize it in some way.

Note: Inheriting AuthorizeAttribute is the recommended way to extend MVC security.

There is just one restriction - your implementation of AuthorizeAttribute must set the filterContext.Result property to null if it succeeds and set it to a non-null value if it fails. If you override the AuthorizeCore() method only, you are covered because the default implementation does this already. However, you must take this logic into account if you override the OnAuthorization() method.

Interoperability with ASP.NET

Sometimes we need to be able to interact with ASP.NET forms authentication as well so we can also hide nodes that represent ASP.NET web forms that are not accessible. In this situation, you can use the roles attribute/property of the node to control which roles have access.

To represent an ASP.NET page, we need to use the URL of that page to represent it.

<mvcSiteMapNode title="Manage Users" url="~/ManageUsers.aspx" roles="Manager,Administrator" />

In this example, only users in the Manager and Administrator roles have access to the /ManageUsers.aspx page.

Note that this works similarly for both Windows and Web forms authentication.

Building a Custom IAclModule

Note: The technique described in this section can only be done using an external DI container.

Sometimes we need to be able to be able to use a custom security scheme other than using AuthorizeAttribute or forms authentication. For this situation, there is the IAclModule extension point. You can implement this interface and inject it using an external DI container.

Here is what a custom IAclModule implementation might look like:

public class MyAclModule
	: IAclModule
{
	#region IAclModule Members

	/// <summary>
	/// Determines whether node is accessible to user.
	/// </summary>
	/// <param name="siteMap">The site map.</param>
	/// <param name="node">The node.</param>
	/// <returns>
	/// 	<c>true</c> if accessible to user; otherwise, <c>false</c>.
	/// </returns>
	public bool IsAccessibleToUser(ISiteMap siteMap, ISiteMapNode node)
	{
		try
		{
			bool userHasAccessToNode = // TODO: Put logic here to determine this
			
			return userHasAccessToNode;
		}
		finally
		{
			// If we cannot reach a conclusion, always return true
			return true;
		}
	}

	#endregion
}

Keep in mind, we are not providing security, we are providing visibility. So if we get an exception, we should just make the node visible as the security provider is responsible for actually locking down the resource.

Once we have implemented IAclModule, then we just need to alter the DI configuration (module) to include it. Here is what that would look like if using StructureMap.

// Configure Security
this.For<IAclModule>().Use<CompositeAclModule>()
	.EnumerableOf<IAclModule>().Contains(x =>
	{
		x.Type<AuthorizeAttributeAclModule>();
		x.Type<XmlRolesAclModule>();
		x.Type<MyAclModule>(); // Added our new module
	});

Note that the IAclModules are processed from the top to the bottom in the list and the first one that returns false wins. If you don't have any interest in using the AuthorizeAtributeAclModule or XmlRolesAclModule after implementing your own, you can safely remove them.


Want to contribute? See our Contributing to MvcSiteMapProvider guide.



Version 3.x Documentation


Unofficial Documentation and Resources

Other places around the web have some documentation that is helpful for getting started and finding answers that are not found here.

Tutorials and Demos

Version 4.x
Version 3.x

Forums and Q & A Sites

Other Blog Posts

Clone this wiki locally