ASP.NET web.sitemap - roles do not seem to control visibility?

What you want is what ASP.NET refers to as "Site-Map Security Trimming". You have already done most of the work - namely assigning roles to the nodes in the sitemap. Now, all you have left to do, is a bit of configuration.

In your web.config, add a new site map provider using the standard XmlSiteMapProvider, but with securityTrimmingEnabled="true", and make this the default provider:

<system.web>
  <siteMap defaultProvider="XmlSiteMapProvider" enabled="true">
    <providers>
      <add name="TrimmedSitemap"
        type="System.Web.XmlSiteMapProvider"
        siteMapFile="Web.sitemap"
        securityTrimmingEnabled="true" />
    </providers>
  </siteMap>
</system.web>

I suspect you've fallen into the trap we all do, in believing that the roles attribute restricts visibility of the nodes. It doesn't, it actually widens visibility. All restrictions are done with the standard section in web.config.

Full text below is from original post at https://web.archive.org/web/20130408064047/http://ipona.com/asp-net-site-maps-security-trimming-and-roles/ )

This is one of the most frequently asked questions and seems a constant source of confusion for everyone, as it was for me when I first read about it. The ASP.NET SiteMap allows a navigational structure to be defined as a set of XML elements, which are perfect for describing a hierarchy of menu items. These XML items are a siteMapNode element, which has an attribute roles. It seems obvious that this defines the roles that can see this item, but the obvious is in fact wrong. Here is the most important fact about site maps:

The roles attribute does not restrict visibility of a node.

That should be clear enough, even if it still seems wrong. Here’s how it works. All restriction to pages is handled via authorization. You can do this either in the base web.config, or in web.config files in folders. For example, assume there is an Admin folder, under which all the administration pages are kept. You only want these pages accessible to users within the Admin role. You would configure your authorization like so:

<location path="Admin">
  <system.web>
    <authorization>
      <allow roles="Admin" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

The Admin folder can now no longer be accessed by anyone who is not in the Admin role; if you aren’t in the Admin role and try to navigate to a page in the Admin folder, either via link on another page or by typing the URL directly into the browser, you’ll be redirected to the login page. You can have multiple location elements in your web.config, for different folders or even individual files; in fact if you have a restrictive site, you may want to explicitly open up certain pages, such as the login page; it’s hard to login to a site when you don’t have authorization to access the login page. If you prefer not to clutter your base web.config you can create a web.config file in the Admin folder with the same rules; you won’t need the location element since the configuration applies to the current folder.

So that’s authorization done; access to the pages is locked down. Now lets consider navigation. The ASP.NET navigation framework honours the authorization, but only if you configure security trimming on the provider, which isn’t configured by default. This means that you need to add the site map configuration to web.config:

<siteMap enabled="true" defaultProvider="AspXmlSiteMapProvider">
  <providers>
    <clear />
    <add name="AspXmlSiteMapProvider" securityTrimmingEnabled="true"
     type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
     siteMapFile="web.sitemap"/>
  </providers>
</siteMap>

Most of this is configured at the machine level when ASP.NET is installed, but crucially the securityTrimmingEnabled value is set to false by default. What the above does is clear out the existing configuration and add a new entry with the attribute set to true. At this stage the navigation framework will now honour the authorization rules, so menu items won’t be shown if the user doesn’t have authorization for that item; it doesn’t matter if you use a Menu or TreeView to display the menu items, the crucial part is using the SiteMapDataSource (or the Sitemap API if you’re building the menu manually). If you have a custom site map provider, such as a database driven one (such as this one on MSDN), then this might have to do it’s own security checking, but it depends at which base class you inherit from. That’s another story for another post though.

So if you don’t need to modify the site map elements themselves, what’s the roles attribute for? Well this works in the opposite way you probably expect, by opening up visibility of the node, showing the node if the user is in the stated role even if they don’t have authorization to access the page itself (because the authorization rule restrict them from accessing it). Why would you do this? Well, you have to understand how security trimming works. When deciding whether a user can see a node, both the authorization and the physical file permissions are checked; if either fail then the node is deemed inaccessible. There are two very common times when physical file checks fail:

  1. The URL isn’t local. If the file doesn’t exist locally then no check can take place.
  2. There isn’t a URL. The node could be just a container node, with child pages but no page itself.

In both of these cases the physical file checks fail so the node won’t be shown. You therefore may need to open up the visibility of the node. For example, consider the following:

<siteMapNode title="Admin" roles="Admin">
    <siteMapNode url="~/Admin/membership_CreateMember.aspx" title="Create User" />
    <siteMapNode url="~/Admin/membership_GetUsers.aspx" title="View Users" />
    <siteMapNode url="~/Admin/roleManager_CreateRole.aspx" title="Create Role" />
    <siteMapNode url="~/Admin/roleManager_AddUserToRole.aspx" title="Add User to Role" />
</siteMapNode>

Here the Admin node doesn’t have a physical page, it’s purely to allow organisation of the admin items into their own submenu. Without the additional roles attribute the node and children wouldn’t appear, but roles=”Admin” states that the node should also be shown to users within the Admin role, even if the security checking fails. We don’t need the attribute on the child nodes because they have physical pages, so the file checks will succeed.

So it’s fairly straightforward if you remember the rules:

  • Configure security restrictions on pages with authorization in web.config.
  • Redefine the site map provider, enabling security trimming.
  • Add the roles attribute to site map nodes to widen the visibility.

I know this is super old but I inherited some old code and came across the same problem. My boss wanted me to temporarily hide a features visibility from a single role but still make it accessible elsewhere on the site. I fully expected to just remove the role from the sitemap and it not be visible to this role. Eric explained clearly how it's intended to increase visibility rather than limit.

I just wanted to give an example of how to make it function the way you(and I) were expecting it to function(role must be present in both places).

<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" OnMenuItemDataBound="Menu1_TreeNodeDataBound" />

protected void Menu1_TreeNodeDataBound(object sender, MenuEventArgs e)
{
    if (e.Item.DataItem!=null && ((SiteMapNode)e.Item.DataItem).Roles.Count > 0 && HttpContext.Current.User.Identity.IsAuthenticated)
    {
        string role = Common.GetUserRole();  // I have a single role provider and a common function to get the role.  You could always loop through Roles and use HttpContext.Current.User.IsInRole()         
        if (role.Length > 0)
        {
            if (!((SiteMapNode)e.Item.DataItem).Roles.Contains(role))
            {
                if (e.Item.Parent != null)
                    e.Item.Parent.ChildItems.Remove(e.Item);
                else
                    Menu1.Items.Remove(e.Item);
            }
        }            
    }
}