Skip to main content

ASP.NET Menu and SiteMap Security Trimming (plus a trick for when your menu and security don't match up)


Summary

ASP.NET 2005 introduced a pretty solid menu which is integrated with a configuration driven sitemap. The cool part is that the menu can be hooked in with your security roles, so you don't have to worry about hiding or showing menu options based on the user - the menu options are automatically kept in sync with what the user is allowed to see. We'll talk about how to set this up, using an example from a website I worked on recently.
If you're familiar with ASP.NET sitemaps and menus, skip to the end to read a trick for working around cases when you want to do something more complex, such as have a page to be accessible to a user role, but not to show up in the menu.
The Video.Show site was originally planned to have only one class of user, with no user restrictions other than requiring a quick registration before commenting on videos or uploading videos. With that being the case, we just included a static menu in the masterpage, defined as <asp:MenuItem> elements. As we were gearing up for the first beta release, it became obvious that our user model was too simple. Hosted installations would probably want to allow users to register and begin commenting right away, not give all these users upload rights. That implied four classes of user now: unauthenticated, commenter, uploader, and also administrator (implied by the requirement to manage multiple user classes). That meant role management and new menu items to be kept in sync - the right time to move to a sitemap driven menu with security trimming.

Step One - Define The Sitemap

I'm using a static sitemap defined in a Web.sitemap file, and it's especially simple since there's no is no hierarchy involved. This uses the default XmlSiteMapProvider; there are other sitemap providers available on the internets, such as a SQL Sitemap Provider for database driven site structure, or you can implement your provider if you've got a custom situation.
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode roles="*">
    <siteMapNode title="Home" url="~/Default.aspx" />
    <siteMapNode title="Videos" url="~/Tags.aspx" />
    <siteMapNode title="Members" url="~/MemberList.aspx" />
    <siteMapNode title="My Page" url="~/MyPage.aspx" />
    <siteMapNode title="My Recent Views" url="~/RecentViews.aspx" />
    <siteMapNode title="Upload a Video" url="~/Upload.aspx" />
    <siteMapNode title="Administer Users" url="~/AdministerUsers.aspx" />
  </siteMapNode>
</siteMap>
Note that the IntelliSense for a .sitemap file is misleading:
Sitemap Intellisense

Step Two - Declare The Sitemap in web.config

A few things to notice here:
  • I define the provider type as System.Web.XmlSiteMapProvider
  • I specify the siteMapFile as the Web.sitemap file we've just discussed
  • I set securityTrimmingEnabled="true"
<siteMap enabled="true">
  <providers>
    <clear/>
    <add siteMapFile="Web.sitemap" name="AspNetXmlSiteMapProvider" type="System.Web.XmlSiteMapProvider" securityTrimmingEnabled="true"/>
  </providers>
</siteMap>
This is really just overriding the default sitemap settings from %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config, which also uses the name AspNetXmlSiteMapProvider, but which doesn't include security trimming.

Step Three - Set required roles for the pages

This section of the web.config looks long, but you'll see it very repetitive. MSDN's information on setting up authorization rules is pretty well written, so take a look there if you'd like more info. The high points:
  • Rules are processed top to bottom. For example in the Upload.aspx case, a user in the Uploader role is allowed right off the bat, everyone else is denied.
  • Pages which are displayed to all authenticated users just need to deny unauthenticated users, like this: <deny users=?">
  • There's no wildcard for roles, so you can't say something like <allow roles="*">.
  • Role based permissions is configured by default in machine.config (using both AspNetSqlRoleProvider and AspNetWindowsTokenRoleProvider). The Sql Role Provider assumes a database connectionstring named LocalSqlServer, so if your profile information is stored somewhere else you'll need to make sure the rolemanager is configured correctly.
<location path="Upload.aspx">
  <system.web>
    <authorization>
      <allow roles="Uploader"/>
      <deny users="*" />
    </authorization>
  </system.web>
</location>
<location path="Profile.aspx">
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</location>
<location path="MyPage.aspx">
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</location>
<location path="RecentViews.aspx">
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</location>
<location path="AdministerUsers.aspx">
  <system.web>
    <authorization>
      <allow roles="Administrator"/>
      <deny users="*"/>
    </authorization>
  </system.web>
</location>

Step Four - Add A Sitemap Data Source and a Menu to your Master Page

<asp:SiteMapDataSource runat="server" ID="siteMapDataSource" ShowStartingNode="false" />
<asp:Menu runat="server" ID="MainMenu" Orientation="Horizontal" DataSourceID="siteMapDataSource" />
 
You'll probably want to style the menu, too. I'm a fan of the CSS Friendly Control Adapters, which changes the HTML output to use clean UL. Without the Control Adapter, the Menu control outputs nested tables manipulated by JavaScript. Here's what the above menu looks like for a user who's logged in but isn't in the Administrator or Uploader roles:
 Video.Show Menu

The Payoff - Everything is Managed In One Place

That may seem like a lot to configure, and you might be wondering if it isn't easier to just write write your own code to handle access and menu management.
First off, the above actually goes pretty quickly - hopefully this post or others I've linked to make it a little faster.
Secondly, the real payoff is that you've now got a reliable, maintainable solution to controlling page access, and it's all automatically kept in sync. Let's say we want to add a new page that's only visible to users with Uploader rights - maybe a page (MyVideos.aspx) where they can edit or delete videos they've previously uploaded. I'd only need to add one page to the sitemap file, set the access rule in web.config to allow Uploaders and deny everyone else, and the page will only show up in the menu when an Uploader has logged in. This is a good application of the Don't Repeat Yourself philosophy. We don't have one set of logic determining what pages users are allowed to view and another set which determines what pages they should see in the menu; these are both the same list and should be handled that way.

Tip - Use a Url Mapping to alias pages when your access and menu visibility are more complex

I wanted to point out one other tip that came in handy here. Before we realized the need for different user types, we had one page called Member.aspx, which served two purposes. If the querystring contained some other user's userid, it would show their public profile and a list of their videos. We also repurposed it as My Page, determined by navigating to the page as a logged in user without using a querystring.
When we hooked up the menu and page access, we had a problem. We only wanted to show My Page in the menu when a user was logged in, but we needed the Member.aspx page to be viewable by anonymous users, because it was used for public user profiles, too. The simple solution was to set up a Url Mapping which created a virtual MyPage.aspx (mapped to Member.aspx). Now we could set different access rights to MyPage.aspx and Member.aspx, as shown in the Step Three code sample - Member.aspx is public, and MyPage.aspx requires authentication. Here's how the Url Mapping was set up:
<urlMappings>
  <add url="~/MyPage.aspx" mappedUrl="~/Member.aspx"/>
</urlMappings>

Comments

Popular posts from this blog

Asp.Net AjaxFileUpload Control With Drag Drop And Progress Bar

This Example explains how to use AjaxFileUpload Control With Drag Drop And Progress Bar Functionality In Asp.Net 2.0 3.5 4.0 C# And VB.NET. Previous Post  I was Explained about the   jQuery - Allow Alphanumeric (Alphabets & Numbers) Characters in Textbox using JavaScript  ,  Fileupload show selected file in label when file selected  ,  Check for file size with JavaScript before uploading  . May 2012 release of AjaxControlToolkit includes a new AjaxFileUpload Control  which supports Multiple File Upload, Progress Bar and Drag And Drop functionality. These new features are supported by Google Chrome version 16+, Firefox 8+ , Safari 5+ and Internet explorer 10 + , IE9 or earlier does not support this feature. To start with it, download and put latest AjaxControlToolkit.dll in Bin folder of application, Place ToolkitScriptManager  and AjaxFileUpload on the page. HTML SOURCE < asp:ToolkitScriptManager I...

View online files using the Google Docs Viewer

Use Google Docs Viewer for Document viewing within Browser I was looking for a way to let users see Microsoft Word Doc or PDF files online while using my application without leaving their browser without downloading files and then opening it to view with Word or PDF viewer . I was looking for some way out either via any PHP or Microsoft.NET libraries, did some googling on that; but later on I just got an idea that google already has all code written for me.. when I have any email attachment in PDF or DOC or DOCX google does it for me ..! Even while searching I can see PDFs by converting them in HTML. So I just googled it up and found that Google already has this ability that we can use Google Docs Viewer without any Google Account Login . YES that's true no Google Account login is required. It's damn simple and easy. Just pass document path as attachment as parameter and we are done. Google Docs Viewer gives us ability to embed PDF, DOC/DOCX, PPT, TIFF:...

How to send mail asynchronously in asp.net with MailMessage

With Microsoft.NET Framework 2.0 everything is asynchronous and we can send mail also asynchronously. This features is very useful when you send lots of bulk mails like offers , Discounts , Greetings . You don’t have to wait for response from mail server and you can do other task . By using     SmtpClient . SendAsync Method (MailMessage, Object)    you need to do  System.Net.Mail has also added asynchronous support for sending email. To send asynchronously, you need need to Wire up a SendCompleted event Create the SendCompleted event Call SmtpClient.SendAsync smtpClient.send() will initiate the sending on the main/ui  thread and would block.  smtpClient.SendAsync() will pick a thread from the .NET Thread Pool and execute the method on that thread. So your main UI will not hang or block . Let's create a simple example to send mail. For sending mail asynchronously you need to create a event handler that will notify that mail success...