I’ve been talking about this topic since ASP.NET 2.0 was released in 2005 and to be honest this is sort of old news. I never put together a post on it, but since the new SimpleMembership is now released in ASP.NET 4.5 I figured now is time.
Brief History (as I have surmised) about the MembershipProvider.
Back in ~2003 Rob Howard from Microsoft had developed a sample application written in ASP.NET 1.1 showing how ASP.NET (WebForms) could be a viable framework for building common platforms such as web forums comparable to PHP-Nuke. Part of that effort was developing this notion of a “provider” programming model which was an abstract pluggable API for common aspects of the application architecture such as authentication. The idea is that a developer could plugin a custom provider to access arbitrary databases for the users’ usernames and passwords. The hosting application would simply code against this provider abstraction and the custom implementation would be used to contact the database, thus decoupling the two.
Sounds good, eh? So good that the ASP.NET team decided to incorporate the provider model for authentication (membership), roles, user profile, session and other aspects of the runtime into the ASP.NET 2.0 release in 2005. To capitalize on the provider model several new controls were added such as the CreateUserWizard, Login, ResetPassword and ChangePassword controls. These controls, given the provider model, could implement these features without knowing the details of the database being used.
On top of all of this, Microsoft even provided (no pun intended) concrete implementations of the various providers that used SQL Server to store the data (user credentials, roles, profile, etc). So to implement authentication in your application all you had to do was run their SQL script and drag-n-drop a Logon control onto the home page and voilà — security!
Wasn’t that easy? This was touted as the new way to do security in ASP.NET. In fact the membership provider started becoming synonymous with web application security. Also this new provider model was positioned as making security easy in ASP.NET. These last two points is where I have an issue…
What’s important in security?
It’s import to understand how security works in your application so that you can be confident (without a false sense of security) that it’s doing what you need and that you aren’t making mistakes in your application that somehow compromise this security.
By focusing on membership as what security is all about then the other important aspects of web application security are often overlooked. I wrote a bit about this already in how Membership is not the same as Forms Authentication.
Given that the majority of security in a web application is not in the realm of membership, what’s the provider model doing for us? Well, it’s just a database look-up. It’s an abstract API for managing users and their credentials. This is not to say that the storage of credentials isn’t important in web security — it is. I’ll come back to this later.
What’s wrong with the MembershipProvider?
The biggest problem with the membership provider is that the API is a very leaky abstraction. The MembershipProvider is an abstract base class for managing user credentials that has APIs such CreateUser, DeleteUser and ValidateUser (to authenticate credentials). It was originally designed for a forums application and has a total of 27 abstract methods that may or may not be pertinent to your application’s security needs. My favorite of these is GetNumberOfUsersOnline, which makes sense for a forums application (sort of) but otherwise is asinine.
Given the 27 abstract methods, a custom provider would of course be required to implement all of these. In many scenarios if the method isn’t pertinent then a NotImplementedException is the common implementation. The unfortunate part about this is now the application using the custom provider will need to know which methods are not implemented. Even some of the MembershipProvider-derived classes implemented by Microsoft throw NotImplementedException so you have to know which concrete provider you’re using. Herein begins the leaky abstraction.
Another one of those methods on the MembershipProvider is UnlockUser. This was designed into the API to allow an administrator to unlock an account if the provider detects that someone is trying to guess a user’s password. If the provider detects more than some number of guesses then it “locks” the account and prevents further logins. This is a decent feature, except there’s no automatic unlock so this security feature turns out to be a nice denial-of-service attack. Anyway, my complaint is that there’s no comparable LockUser API. What if an administrator decided the user was no longer allowed to login? A way around this would to be bypass the provider and update the database table that contains the flag directly (I suppose another approach would be to try to login as the user with a bad password over and over until their account gets locked). Anyway, this is an example of a missing feature where the API doesn’t meet a security requirement and the solution is to bypass the provider to achieve the goal. This adds to the leaky abstraction.
Given that the MembershipProvider is a base class, people are often misled into thinking they can derive from it if they need to augment the semantics (either for custom APIs or additional data stored with the user). One example is requiring more than username and password for authentication (such as a RSA secure ID for multi-factor authentication). To support this why not add a new ValidateUser that accepts three arguments instead of two? Technically it’s possible, but the built-in Login control isn’t designed for three parameters (let alone the CreateUserWizard). So you’d have to build your own controls for your custom parameters then then would have to downcast the provider base class to the custom provider class and manually invoke the custom ValidateUser method. All while pigeon-holing yourself into this provider model that doesn’t meet your needs (remember 27 abstract methods that may or may not be pertinent). Yet another leaky abstraction.
Another complaint (albeit a bit dated at this point) is that the SqlMembershipProvider is notusing modern password storage techniques. This seems strange since in the same .NET 2.0 release in 2005 the Rfc2898DeriveBytes class was released which is the proper way to generate a password hash. My understanding is that the newer universal providers do use this and those are configured by default in the new project templates.
If you’re using MVC we don’t use the control model of WebForms. This means that much of the purported re-use of the provider model is lost due to the lack of Login, CreateUserWizard and other controls. The project templates so make up for some of this in MVC, though.
Lastly, Membership doesn’t follow SRP which forces provider implementers to violate DRY (which so highly regarded these days by modern developers). The logic of membership (mainly password management and account locking) should be decoupled from the rest (mainly the storage piece). Decoupling these two would allow for reuse of the good principals of account management while allowing alternative storage mechanisms without needing to re-implement the security parts. When I build my security systems, I build an account service that uses a user repository. This provides a nice separation of concerns.
For about a year or two after ASP.NET 2.0 was released on every new project I attempted to use membership with a good-faith effort, and on every project at some point our requirements exceeded what the membership API anticipated. We had to back out the code using the membership provider and build our own plumbing for storing user credentials.
What’s good about the MembershipProvider?
The good part about the membership provider isn’t the model per-se, but rather how the built-in implementations provide password management. When storing credentials, proper password hashing is crucial. This is the one useful thing from the provider model that you’d need to implement if you’re doing your own framework for storing credentials. Fortunately since the release of MVC 3 (and really Razor and WebMatrix) there is a Crypto class that provides modern APIs for password management and validation. So one more reason that the provider model is not needed.
What about SimpleMembership?
There are some nice additions like the account reset token and the extensions to support OAuth/OpenID, but unfortunately I don’t think SimpleMembership improves on the situation. The SimpleMembership class ultimately derives from the MembershipProvider base class so it built upon the same house of cards. It seems obvious why it was designed this way — Microsoft wanted to provide some amount of backwards compatibility so that old code could still call into the new SimpleMembership implementation, but it’s still the same leaky abstraction issue.
Now the way they attempt to wean developers off of the leaky MembershipProvider API is they have introduced a new WebSecurity API that delegates to the SimpleMembership provider. This helps from a consumer perspective, but if you want your own database then you still have to implement a MembershipProvider and now we’re back to the leaky abstraction. Also the WebSecurity API continues to the blur the distinction between what the provider model is doing and what forms authentication is doing.
Also, another issue with SimpleMembership is that it has the same coupling of account management logic and persistence. SimpleMembership contains hard-coded SQL statements to build tables and performs selects, inserts, updates and deletes against their schema (and some of yours as well). The way it was coded, SimpleMembership is tied to a Microsoft database (SQL Server, SQL Azure, etc). It would have been nice if they could have decoupled the logic for supporting reset tokens and OAuth/OpenID account associations from the persistence of said data. If you want to store this data in another database you not only have to re-write the persistence code but you also have to re-write the same logic that’s in the SimpleMembership provider (so essentially the same SRP/DRY complaint above about the MembershipProvider). If you have to rewrite all of this logic anyway, then why pigeon-hole yourself into the leaky MembershipProvider abstraction?
It’s too bad since they had an opportunity to make a clean break.
If the membership provider API models exactly what you want and need in storing user credentials, then great — use it. But if you’re building a more complex application than your golf league website then chances are that you’ll need a bit more control over how this data is being maintained in the database.
The goal was to simplify but given the abstraction model and all the layers of Membership I find that people get confused and focus on the wrong details of security. I applaud Microsoft for the attempt… really. The membership provider model is an almost impossible API to design for everyone else’s security needs and that’s unfortunately why it so often falls short.