In a recent moment of clarity I finally realized why I've been having so many problems with ASP.NET's Membership and Role Providers. Surprisingly enough, it wasn't due to their extreme age. Instead, I determined that the reason I've been having so many problems with them was due to the fact that I've been using them incorrectly for years now.
My Experience with Providers
I still vividly remember how excited I was when I first learned of providers. (It was at a private software review on the Microsoft campus where Scott Guthrie was showcasing how cool it was now going to be for ASP.NET developers to just focus on their apps by leaving behind all the headaches associated with wiring up logins and rolling security functionality from near scratch all the time.) Over the years I've leveraged ASP.NET Membership and Role providers in a large number of sites and projects.
What I've liked about providers over the years is that fact that they were extensible—and let me define my own users and authorization. In other words, instead of using these providers out of the box (against access or attachable SQL Server databases), I've almost always used providers to either work against schema that I create, schema and data that already exist, or a non-database–driven set of authentication and authorization needs.
Only, over the last few years (especially since switching to ASP.NET MVC development and taking a more agile approach to development, I've noticed both how ASP.NET's providers are really starting to show their age and how they incur lots of negatives that really offset the only two benefits (authentication and authorization) that I've drawn from them.
My Problems with Providers
Among other things, one of the biggest problems I've had all along with ASP.NET's Membership and Role providers was the huge number of non-implemented methods that I'd always end up with when creating my own customized providers. Without creating customized providers, I'm at the mercy of ASP.NET—either in the form of running a few scripts against an existing database that would wire it up to store information about users and roles in schema that was written years ago (and which doesn't meet my picky standards or tastes) or in the form of blindly dropping that data into another, siloed database somewhere. Neither of those approaches has ever really met my business needs.
Furthermore, while the ASP.NET Membership provider provides methods for adding new sign-ups (or registering new users), changing passwords, retrieving lost passwords, and all that "stuff," I've never really found myself building the kind of application where I could just use those methods off the shelf. In fact, I still, personally, maintain that those methods are really only scoped or suited for a very subset of website types—such as forum sites and other "self-service" type sites. But in my case, creating sites has always required a more involved registration process, required payment, or needed to address business rules that were more complex than just letting end users self-register (to say nothing of the extra effort required to tweak custom providers in terms of password complexity or to allow usernames to be email addresses, and so on).
Consequently, whenever I end up implementing Membership or Role providers, what I always end up with is a single method (ValidateUser) that is implemented in my Membership provider, and a single method (GetRolesForUser) in my Membership providers as well. Otherwise, the other 37 members in these providers end up being "implemented" with "throw new NotImplementedException();" code.
That, and there's really no way to get around the fact that these providers end up being static objects—instantiated when the site first launches, and lingering around in memory until the app/site restarts. And, granted, I'm not worried about the performance overhead of having objects scoped that long, but as I reported previously, the static nature of these objects has burned me due to some poor choices on my part when it came to combining Linq to SQL contexts that cached data within these objects.
The True Nature of Providers
In the end, my moment of recent clarity was really two realizations.
First, that ASP.NET membership (and role management) really isn't about managing security: It's about managing users. Hence, the reason I've felt, for so long, that I've been kicking against the bricks with these providers is because I've been using them wrong, as I really don't need or want ASP.NET to control my membership. As a developer, I consider membership to be my responsibility—or something that almost always has to be managed via a set of complex business rules that I have to translate into each application.
Second, since all I really wanted out of these providers was the ability to validate users against my own data and needs, and then authorize these users (against their FormsAuth ticket) once they had been validated, it occurred to me that using providers (and all their extra bulk) to accomplish this was dumb. In fact, I'm actually amazed it took me so long to figure out that I've been waiting all this time for Microsoft to update these providers so that they would (frankly) suck less, when, in fact, I've just been using them wrong all along.
Or, in other words, if all I wanted out of providers all this time was the security aspects that they provided, then I've been barking up the wrong tree—because while providers do provide security, they're primarily associated with the management of your membership. (Which is why there are so many methods in the Membership provider focused on creating, modifying, deleting, and finding membersand only a single method for validating a member.)
In all fairness, it looks like the ASP.NET team has also identified that what many developers really want out of membership is just security, to the point where they've tried to streamline things a bit with the introduction of the Simple Membership Provider. Only that, too, suffers from the problem of being so tightly bound to SQL itself—which means it also suffers from something else many developers want or need, such as the ability to use OpenID, Active Directory (AD), or other membership stores that aren't RDMBS related (such as NoSQL).
Building My Own Membership Service
Within about 30 minutes of realizing that all I needed was a way to easily authenticate and then authorize my users, I was able to create a working prototype of what I needed. To pull this off in a new sample MVC3 application, I just modified the IMembershipService interface defined in the AccountModels.cs file that gets dropped into place when you pick the non "empty" MVC3 template. Long term I think I'll likely move this interface deeper into my own code or projects, as I think that the models defined in AccountModels.cs should really just be treated as abstractions (or seams) to better handle unit testing.
Otherwise, though, all I needed this interface to really provide was two methods, ValidateUser() and GetRolesForMember, as outlined below:
public interface IMembershipService
bool ValidateUser(string userName, string password);
List<string> GetRolesForUser(string userName);
Then, by creating a concrete implementation of that service, I was able to easily wire up an example of a ValidateUser() method (which takes in a username and passphrase). Best of all, rather than having to deal with anti-patterns or parameter injection (as I would have to do with a Membership Provider to get this same functionality), I was able to wire my IMembershipService up for dependency injection since it's just a normal class (or service) instead of a monolithic component that gets instantiated when my app starts up via a default, parameterless constructor as is the case with a Membership Provider.
This, in turn, ended up being insanely easy—and literally only took a few minutes—because the existing workflows within the "default" MVC3 application are that once an IMembershipServices' .ValidateUser() has returned true, the user ends up being logged in by being passed off to FormsAuth.
Consequently, all I then needed was the ability to evaluate the role membership of authenticated users by means of a custom FilterAttribute. And, amazingly, the hardest part about creating this functionality was coming up with a good name for the attribute—because the MVC team has done a fantastic job of making it very easy to override the actual membership checks within the AuthorizeAttribute.
In fact, if you're familiar with MVC's AuthorizeAttribute, you know that one of the big benefits that it provides over rolling your own authorization attributes is that it handles caching concerns as well (such that you don't have to worry about authorized end users caching responses that could then be "leaked" to non-authorized or non-authenticated users).
Consequently, you either have to pay attention to this logic yourself if you roll your own attribute from scratch, or if you poke around in the source code for this attribute itself, you can see that not only has the ASP.NET team designed this class to be extended, but they've gone the extra mile toward making it easy to extend while preserving the inherent logic they've provided to make sure you don't do something dumb with the cache.
As such, I wasted no time in creating my own "SiteAuthorizationAttribute" that inherits from AuthorizeAttribute, uses IoC to Parameter Inject an instance of my IMembershipService into the attribute, and then overrides the AuthorizeCore method to query the MembershipService's list of roles for a given user—instead of delegating out to the statically wired RoleProvider (as defined in the web.config) to handle this task. (Info on how to get a copy of my sample implementation is provided below if you're interested.)
Benefits of Rolling My Own Membership Service
I'm still just barely getting rolling down the path of using this new approach to validating and authorizing users. So, I'm sure that as I do more with it I'll spot ways in which it might need to be refined, modified, or tweaked. But for now I love the fact that I don't have all those non-implemented methods lying around, and that I've got a single service (IMembershipService) that handles authentication and authorization—instead of splitting those tasks over two monolithic providers. Likewise, dependency injection in this regard seems easier (mostly thanks to MVC3 though—not really to my new approach), and even though I haven't really worked on a prototype, I'm pretty sure that building a provider for OpenID, AD, or anything else would now be much easier to pull off as well.
What I Learned
In the end, rolling your own logic and code to validate users and to determine authorization is easy. I'm sure gobs of ASP.NET developers have done it. So my sample app may not be of concern to many folks. I'm still making it available for anyone who's interested though—so just email me if you'd like a copy.
Otherwise, the reason I shared my adventures was to hopefully underscore two key things:
First: ASP.NET MVC is insanely simple to tweak, extend, and customize to meet your needs. Better yet, not only was I able to quickly extend the AuthorizeAttribute to meet my own specific needs, but I only had to override a single method—because the entire class has been so well written in terms of encapsulation and sticking to the principles of single responsibility. So, once again: Kudos to the MVC team for constantly doing such a great job.
Second: Sometimes we, as developers, can settle for things that work, without really even thinking about them too much. Consequently, if something you're doing over and over again is giving you grief, you might want to be smarter than I was and pause for a minute and determine whether you're using the best solution for the job. The solution I've been using for years worked, but it wasn't the best solution for the job.
Got anything in your dev toolbox that you blindly use without thinking about? Even when it gives you a bit of pain here and there? If so, maybe it's time for some renovations.
Michael K Campbell ([email protected]) is a contributing editor for SQL Server Magazine and a consultant with years of SQL Server DBA and developer experience. He enjoys consulting, development, and creating free videos for www.sqlservervideos.com.