Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

SimpleMembershipProvider vs MembershipProvider

4.86/5 (16 votes)
31 Aug 2013CPOL6 min read 47K  
Everything you ever wanted to know about why SimpleMembershipProvider does not work as a MembershipProvider.

Introduction

This article should really be called, everything you ever wanted to know about why SimpleMembershipProvider does not work as a MembershipProvider.

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 NotSupportedException

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.

Background

The 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.

The new 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.

To see 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 WebSecurity.InitializeDatabaseConnection routine.

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.

If enableSimpleMembership is set to true in the Web.Config, then WebSecurity will override any default providers with SimpleMembershipProvider and SimpleRoleProvider.

If enableSimpleMembership is not set, then WebSecurity will attempt to initialise any existing ExtendedMembershipProvider or ExtendedRoleProvider instances configured via the Web.Config, but only where they are configured as the default provider.

If 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.

Compatibility

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.

Login Beware

Related to compatibility issues described above, there are additional consequences when using the SimpleMembershipProvider in the context of MembershipProvider

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:

C#
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
    if (!ModelState.IsValid) 
    {
        ModelState.AddModelError("",
           "The user name or password provided is incorrect.");
    }
    else if (WebSecurity.IsAccountLockedOut(model.UserName, 10, 3600))
    {
        ModelState.AddModelError("", 
           "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))
    {
        return RedirectToLocal(returnUrl);
    }

    return View(model);
}

Missing Support

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:

  • CreateUser
  • GetUser (by providerUserKey)
  • GetUserNameByEmail
  • FindUserByUserName
  • FindUserByEmail
  • GetAllUsers
  • FindUsersByName
  • FindUsersByEmail
  • UnlockUser
  • ChangePasswordQuestionAndAnswer
  • GetNumberOfUsersOnline
  • GetPassword
  • ResetPassword

Conclusion

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)