Geeks With Blogs
Chris Falter .NET Design and Best Practices

Vinegar is an important ingredient in the kitchen; with it you can make baked tropical spareribs, Indian mutton curry, orange almond greens and a host of other delightful dishes.  Baking soda, too, is an essential ingredient in nutmeg raisin coffee cake, buckwheat pancakes, and many other delectable baked goods.  Both are useful, reliable, and safe in storage.  However, as any fourth-grader who has ever attended a science fair can tell you, all you have to do is mix the two to create a volcano that will spill lots of red, flowing "lava" all over your kitchen tile.

Today one of my colleagues inadvertantly created a code volcano when he incorrectly mixed Windows authentication with an ASP.NET 2.0 website project.  (Of course, I was responsible for reviewing the code [Ahem!] so I'm in no position to point fingers.)

The site has the responsibility of displaying GIS data to internal users.  Certain of the data are restricted to users who belong to certain Windows groups; the restriction is managed through database views.  My initial thought was to create a separate directory for each group, and to let each page in a directory derive from a custom page class which would define the database view to use.  Then we could write a location element in web.config with a directory-specific authentication element (under system.web) to ensure that only the members of the appropriate group could view the directory's pages. 

But my colleague had a much better idea; the main site could select the appropriate view based on the group membership(s) of the logged-in user.  The code he checked in this morning for the site's base page class contained the following:

    Public ReadOnly Property UserGroups() As List(Of String)
            Dim groups As List(Of String) = New List(Of String)()

For Each idref As IdentityReference In WindowsIdentity.GetCurrent().Groups

            Return groups
End Get
    End Property

The page class would then check for group memberships (in order from least restrictive to most restrictive) in the List<string>.  It would select the database view that corresponded to the first match.  If it did not find a match, it would transfer the user to an error page ("Unauthorized.aspx").

To test this design my colleague ran the site in debug mode on his machine, and saw that he was able to use the view corresponding to his group membership.  To test users with other group memberships, he turned on Windows impersonation in web.config...

<identity impersonate="true" userName="MyDomain\TestGroupMember" password="TestGroupMember"/>

...and everything worked as expected.  He commented out the identity node in web.config, checked in his code, and we promoted it to a test environment.

Knowledgeable readers can anticipate what happened next: when I accessed the site's index.aspx on the host server, I got a chance to review the look-and-feel of Unauthorized.aspx, even though I was a member of the least restricted group.

So why was the behavior different running on IIS than on the developer machine?  On a developer machine, the website is hosted in a process called WebDev.WebServer.EXE running under the identity of the Visual Studio 2005 user.  On Windows 2003, though, the website is hosted by an app pool running under the identity of whatever's configured in IIS (by default, the server's "Network Service" user).  The UserGroup property's call to WindowsIdentity.GetCurrent() returns the Windows identity of the host process, *not* the WindowsIdentity of the authenticated user.  When the code ran on a developer machine, this was not a problem; the identity of the site's user and the host process were identical.  But when the code ran on a server, it would try to find a database view associated with the "Network Service" user.  And since none existed, it would refer the user to Unauthorized.aspx. 

(Note to self: never repeat the mistake of testing user logins via identity impersonation.)

Once we understood the error, correcting it was not too difficult.  My colleague simply called the (System.Web.Security) Roles.IsUserInRole() method, passing in the role names from least restrictive to most restrictive.  Behind the scenes, ASP.NET is basing its role management on the logged-in user's roles.  When a site is configured to use Windows authentication, the user's roles are the same as the user's Windows group memberships.  Now everything works the way it's supposed to, and Daddy can go home and have dinner with his family.

Posted on Friday, November 2, 2007 10:01 AM .NET Gotchas | Back to top

Comments on this post: Website Projects and Windows Authentication: a Volatile Mixture

No comments posted yet.
Your comment:
 (will show your gravatar)

Copyright © Chris Falter | Powered by: