Some years ago, I built a commercial ASP.NET 1.1 application and part of it is about to be sold off. I've been the sole developer on it since its inception but haven't spent much time on it because it hasn't been a major focus for the person for whom I built it.

Now, after being tasked with preparing a stripped down version of it that someone else will be looking at, I'm starting to realize just how far my development skills have come in the last few years.

But that's not what I came to talk about.

The application is small enough that I'm taking the opportunity to refactor a little as well as upgrade to .NET 2.0. Not using the upgrade wizard, mind you. Last thing I want in a .NET 2.0 app is a bunch of designer code intermingled with the page logic. And let's just say I wasn't exactly a proponent of MVC back then, let alone MVP. Heck, at the time I was just proud to be using the DAAB (the original version, with the static SqlHelper).

One of the things I wanted to tackle was cleaning up the authentication code to take advantage of the ASP.NET security controls. In the original version, I used what I think was fairly standard practice at the time. Namely, in the AuthenticateRequest event in Global.asax, I created a GenericPrincipal using the username and roles from the application's database. (Oh yeah, I'm using Forms Authentication.) This was pretty useful in creating a guest-only version of the app as well as an admin section.

With .NET 2.0, I had read and played with the Membership and Role Providers but hadn't done any real work with it other than to run through the ASP.NET configuration wizard and stare blankly at the sheer number of tables created to support it.

In this version, I had two goals: a) use those providers, and b) do NOT, under any circumstances, create that aspnet_regsql security schema in my database. After all, I already had a user/role schema set up that worked.

So starting from Scott Guthrie's comprehensive list of ASP.NET security resources, I began wading through tutorials each of which wants you to either use that same schema, or use some entirely different backend. All I really wanted was to use the SQL providers with my own schema.

It was this article that got me on the right track. From there I was able to determine what I had to do. To wit, I created a custom membership provider and a custom role provider to do only what I need them to do.

The membership provider derives from SqlMembershipProvider and contains only the following code:

    public override bool ValidateUser( string username, string password )
    {
        string role = MyDal.ValidateUser( username, password );
        if ( role == null )
        {
            return false;
        }
        return true;
    }

Similarly, the role provider derives from SqlRoleProvider and contains the following:

    public override string[] GetRolesForUser( string username )
    {
        ArrayList roleList = new ArrayList( );

        DataTable roleTable = MyDal.GetRolesForUser( username );
        foreach ( DataRow row in roleTable.Rows )
        {
            roleList.Add( row["UserGroup"].ToString( ) );
        }

        return (string[])roleList.ToArray( typeof( string ));
    }

(NOTE: If I may use my Hillbilly mind powers for a moment: You WILL ignore the data access code.)

These methods do exactly what they say they do but they are doing it the way *I* want them to do it. I.E. They're retrieving the data from my database using stored procedures of my own design.

After that, it was a simple matter of registering the providers in the web.config:

    <membership 
          defaultProvider="MySqlMembershipProvider" >
        <providers>
            <add name="MySqlMembershipProvider" 
                 type="MySqlMembershipProvider"
                 requiresQuestionAndAnswer="false"
                 connectionStringName="MyConnection" />
        </providers>
    </membership>
    <roleManager enabled="true"
                 defaultProvider="MySqlRoleProvider">
        <providers>
            <add name="MySqlRoleProvider"
                 type="MySqlRoleProvider"
                 connectionStringName="MyConnection" />
        </providers>
    </roleManager>    

Where "MyConnection" refers to an entry in the <connectionStrings> section.

Now I'm able to use the ASP.NET login control and the various menu and navigation controls using security trimming as well as protect my application without having to fiddle around with the forms authentication cookie.

Note that this works splendidly for my scenario. In my application, users log in and the system determines what role (that's right, I'm supporting only a single role, you wanna make something out of it?) they're in. That's it. There is no "I haven't registered yet so I'll just sign up online" or "I forgot my password can you send it to me?" or "What users are in this role?" scenarios. That's why I can get away with overriding only the two methods listed above.

Like JP says, small victories...

Kyle the Victorious

P.S. I still hate security-related coding