This article should really be called, everything you ever wanted to know about why
SimpleMembershipProvider does not work as a
Never trust a class with the word simple in it's name, it sets expectations that are rarely met, and the
SimpleMembershipProvider is no exception. The new provider may extend from the
MembershipProvider abstract class, but it certainly cannot be used with the older membership system for the following reasons:
- Simple providers must be explicitly/implicitly configured as default providers
- MembershipUser isn't fully implemented, and only UserId/Username properties are mapped
- Membership based IsApproved/IsLockedOut functions aren't supported/mapped to new APIs
- Membership's ValidateUser returns true in the case of a locked out account
- Core functions of membership will result in a
For most projects these points probably don't matter, however there are times when this becomes an issue. For instance when you need to leverage the plugable nature of providers for integration into third party systems, such as a CMS.
My own experience relates to using
SimpleMembershipProvider with Sitecore. We wanted to leverage new features of the simple provider on the front-end, but still use Sitecore's User Manager features on the back-end. The good news is that it is possible to overcome these issues, and in a later article I'll open source my own alternative providers.
SimpleMembershipProvider was introduced by the WebMatrix team, and is touted as the future of ASP.NET membership. In reality membership will soon be superseded by ASP.NET Identity. In the mean time simple providers move away from the
Membership/Roles interfaces, and introduces a cleaner
WebSecurity API. In doing so it breaks away from the traditional username/password based authentication scheme, and introduces support for federated authentication.
WebSecurity framework also ties in with Entity Framework to provide an extendable entity model with support for Code-First, and can even bind to existing user tables/schema. The removal of Stored Procedures from the system means support for all SQL Server based products, including Azure and Sql Server CE.
It's worth noting that support for extended SQL Server products is also available with the new Universal Providers, but these lack the further enhancements described above and work only on a pre-defined membership schema.
SimpleMembershipProvider in action, crack open Visual Studio and create a new instance of the MVC 4 Internet Application template. You'll notice the
AccountController exclusively uses the new
WebSecurity class which in turn makes use of the new
ExtendedMembershipProvider definition which
SimpleMembershipProvider derives from.
You may also expect to see the
SimpleMembershipProvider registered in the
membership section of the
Web.Config, however this is not the case.
The Simple Magic
The supposedly simple
SimpleMembershipProvider comes with a degree of magic. Sigh... why wouldn't it? It is possible to register the provider explicitly via the
Web.Config, however the new
WebSecurity API may also do some auto-wiring behind the scenes. This is controlled via the
enableSimpleMembership appSetting, and the
Regardless of how
SimpleMembershipProvider is registered, any attempt to use the
SimpleMembershipProvider without first calling
WebSecurity.InitializeDatabaseConnection will result in the following exception:
You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class. This call should be placed in an _AppStart.cshtml file in the root of your site.
The reason the provider must be initialised is so that the application can map to any custom database schema, and to enable Entity Framework to initialise the database for Code-First.
enableSimpleMembership is set to
true in the
WebSecurity will override any default providers with
enableSimpleMembership is not set, then
WebSecurity will attempt to initialise any existing
ExtendedRoleProvider instances configured via the
Web.Config, but only where they are configured as the default provider.
enableSimpleMembership is set, but
WebSecurity.InitializeDatabaseConnection is not called, then the simple providers will wrap & replace any existing
AspNetSqlMembershipProvider/AspNetSqlRoleProvider instances. In this case extended features available on the
WebSecurity API will result in the not initialised exception. Calls to the older membership APIs will pass through to the wrapped AspNetSql providers.
This leads to the first issue:
Simple providers must be explicitly/implicitly configured as default providers.
In most cases this is not a problem, but for systems like Sitecore which can use multiple providers simultaneously it's a real blocker.
The next problem is compatibility with the old membership system. To support
WebSecurity the new system requires an instance of
ExtendedMembershipProvider which extends from
MembershipProvider. However, although the simple provider is a
MembershipProvider in it's own right, the older API is somewhat broken if used as such.
The reason for this is that many of the older membership database columns have been removed, such as
LastLoginDate. In addition
WebSecurity supports a new token based account confirmation function, and introduces new password lockout behaviour that isn't naturally compatible with the older system.
The consequence of these changes is that the
MembershipUser returned when using the older APIs now only supports UserId/UserName. Other properties such as Email, IsApproved, CreatedDate, IsLockedOut are hard-wired to return default values. When used as designed it's not an issue, as the new system relies on Entity Framework models rather than
MembershipUser to expose data back to the application. It's only when
SimpleMembershipProvider is used in the context of
MembershipProvider that functionality is lost. For instance, the ability to unlock a locked account, or even the ability to create a new user account. These scenarios are now only possible via the
WebSecurity APIs, and plugging the provider into an existing user management interface written for the old system won't work.
Related to compatibility issues described above, there are additional consequences when using the
SimpleMembershipProvider in the context of
As password lockout functions are now re-engineered to be based on a timeout and max retry count, it means the old system has no visibility on these states. As such
ValidateUser method will now return success even if the account is locked out. This is actually consistent with the new APIs which moves responsibility for checking the lockout state to the application, however it will certainly introduce security flaws when plugged into code built around the original membership system.
Incidentally the MVC 4 internet Application template is missing the locked out account check on login. This can be added as follows:
public ActionResult Login(LoginModel model, string returnUrl)
"The user name or password provided is incorrect.");
else if (WebSecurity.IsAccountLockedOut(model.UserName, 10, 3600))
"You have entered a password incorrectly too many times. Try again in an hour.");
if (ModelState.IsValid && WebSecurity.Login(
model.UserName, model.Password, persistCookie: model.RememberMe))
Lastly, the simple providers do not support many of the old APIs, and will return
NotSupportedException if called. Presumably these functions were too difficult to implement without Stored Procedures, or don't fit in with new schema/methodologies of the new design. Unsupported
MembershipProvider functions are:
- GetUser (by providerUserKey)
On the surface it looks like
SimpleMembershipProvider provides the best of both worlds by extending on the existing
MembershipProvider. In reality there is no backwards compatibility of any value offered by this relationship, and security flaws could be easily introduced if used in this scenario.
In a future article I will provide a more compatible set of providers that solves a number of issues raised in this article, and will hopefully bridge the gap between the interfaces.