.NET Land: The Trouble With ASP.NET Default Authentication Providers

A Python guy learns to swim in the waters of ASP.NET. In this update: authentication!

About this series

Currently at my job I’m helping another developer implement a “line of business” application in Silverlight 4.0. Although I’m usually a Python developer I have done some .NET development in the past and so I have a passing familiarity with Visual Studio and C#. To say that I have had some challenges in learning this new environment would be the understatement of the year however. This is one in a series of blog posts chronicling my journey through .NET-land.

Today’s small victory came when I was able to get Active Directory authentication working in our staging environment. We try to do all authentication here through our Active Directory setup in order to save our users and ourselves on the IT staff the hassle of having to manage multiple user accounts, plus it’s pretty well supported by most 3rd party applications out there. The documentation is not great though, which I’m finding is an unfortunate fact of life in the world of Microsoft technologies.

So wiring up AD authentication in the project was not too difficult. I more or less followed the steps in Chapter 8 of the very good book “Pro Business Applications with Silverlight 4” by Chris Anderson. Really all it comes down to is adding an Authentication service containing a class that inherits from the generic AuthenticationBase<T> and then in the web.config for your web project configuring that service to use the ActiveDirectoryMembershipProvider rather than the SqlMembershipProvider. Easy peasy and it worked straight away in debug mode within Visual Studio. I naively considered it to be “working” and told my co-worker so. (Brief aside here: I’m not great at unit testing although I really am working hard to get better at it, but Visual Studio’s setup and workflow really do not lend themselves well to unit testing at all, so I’m afraid it’s reinforcing my bad habits.)

That evening he emailed me to tell me that authentication was not working in our staging environment. I tried it myself and it was true: I fed it my credentials and was greeted with a System.ServiceModel.DomainServices.Client.DomainOperationException containing a query 'Login' failed message.

No stack trace.

And here I was confronted with the full enormity of the scope of things I DO NOT KNOW about the .NET world.

Where to even begin? The documentation for this exception is monumentally unhelpful. In addition, the fact that it is such a general exception (it seems that the WCF stack will wrap pretty much any unhandled exception with this exception) makes it difficult to find anything useful by Googling the error message. The lack of a stack trace meant I had no idea at what point the error was occuring. The fact that it worked inside Visual Studio but not in IIS was somewhat useful information but raised even more questions. Is it permissions? Is it because SSL is not currently enabled on the staging environment? Is it some misconfiguration deep in the bowels of the IIS management UI? Did we not deploy the web application properly? etc etc etc.

My first step in solving the problem was discovering that if I entered bad credentials in the login UI, I actually received the “invalid username/password” message that I expected. So that told me that the AD authentication piece was actually working properly and the error was occuring after authentication, which I would expect meant some sort of database interaction was going badly. I started then thinking about the other components that I had seen mentioned in the book chapter I read but had not bothered to investigate more fully, the role manager and the profile provider. I had enabled these in the web.config file but not provided any configuration information, and they worked in local debug mode. However, it seemed to me that these components would be hitting the database and yet I had not provided any connection string or anything like that in web.config… so how were they working in local debug mode? For reference, here is what the relevant parts of my web.config looked like at this time:

    <system.web>
      <!-- snip -->
      <authentication mode="Forms">
        <forms name=".FOO_BAR_ASPXAUTH" />
      </authentication>

      <profile>
        <properties>
          <add name="FriendlyName"/>
        </properties>
      </profile>

      <membership defaultProvider="MyADMembershipProvider">
        <providers>
          <add name="MyADMembershipProvider"
          type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
          connectionStringName="ADConnectionString"
          attributeMapUsername="sAMAccountName"
          connectionUsername="xxxxxxxx"
          connectionPassword="xxxxxxxx"
          connectionProtection="Secure"
           />
        </providers>
      </membership>
    </system.web>

Long story short, after a couple of missteps with web.config I was able to get a bare minimum of required configuration to get these components to work: each needed a name, a database connection string, and a type/assembly reference. Also I had to run the “aspnet_regsql.exe” wizard that ships with ASP.NET to create the required tables and stored procedures that these providers rely on. After putting the correct values in place and publishing the application, it worked! The relevant sections of my web.config:


<system.web>
  <!-- snip -->
  <authentication mode="Forms">
    <forms name=".FOO_BAR_ASPXAUTH" />
  </authentication>
  <profile defaultProvider="MyProfileProvider">
    <providers>
      <add name="MyProfileProvider"
           connectionStringName="MyConnStr"
           type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
    <properties>
      <add name="FriendlyName"/>
    </properties>
  </profile>

  <roleManager enabled="true" defaultProvider="MyRoleProvider">
    <providers>
      <add name="MyRoleProvider"
            connectionStringName="MyConnStr"
            type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
  </roleManager>
    
  <membership defaultProvider="MyADMembershipProvider">
    <providers>
      <add name="MyADMembershipProvider"
    type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
      connectionStringName="ADConnectionString"
      attributeMapUsername="sAMAccountName"
      connectionUsername="xxxxxxxx"
      connectionPassword="xxxxxxxx"
      connectionProtection="Secure"
        />
    </providers>
  </membership>
</system.web>

Lessons Learned