First: After searching for awhile to figure out what’s new/different with MVC 4 and forms authentication, this is the best article I've found on the subject:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx
Some quotes from the article:
I've been trying to figure out the Forms Authentication in MVC4. It's different than the past approach (aspnet_regsql). If you do file new project -> MVC 4 -> internet application, you get a really nice template with the controller and model setup for you. However, the tables are different than using aspnet_regsql and the ASP.Net Configuration tool (WSAT) wasn’t connecting to the data I had (it was creating an App_Data/aspnet.mdf file, which I didn’t see right away).
Points of Note
- The database tables are created in the InitializeSimpleMembershipAttribute.SimpleMembershipInitializer class by the WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true); method.
- The tables created are webpages_Membership, webpages_OAuthMembership, webpages_Roles, webpages_UsersInRoles, UserProfile.
- Web.config settings won’t work.
- If you want to handle lockout based on the number of failures, you can call Websecurity.IsAccountLockedOut: see http://stackoverflow.com/questions/12206192/simplemembershipprovider-websecurity-configuration
- http://stackoverflow.com/questions/12702649/simplemembershipprovider-minrequiredpasswordlength-always-returns-0-despite-it-b ~ the answer states you can’t use the web.config settings - When adding non-nullable values to the UserProfile, you also need to modify the AccountController.Register method, or you wont’ be able to create a new user / it will just have the defaults set. Here I’m using a ComplexType and setting them before creating the User (I’m moving this to a method in the UserProfile for a refactor):
var values = new { ModInfo_CreatedDate = DateTime.UtcNow.ToString(), ModInfo_CreatedByUserId = 0 };
WebSecurity.CreateUserAndAccount(model.UserName, model.Password, values);
-
Roles (10/31/12): I was trying to use User.IsInRole and it was always returning false. I found this post that helped me get past this on Stack Overflow: http://stackoverflow.com/questions/12257232/role-based-authentication-in-the-new-mvc-4-internet-template-using-simplemembers. 1) add the <roleManager to the web.config, which leads to the error “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.”. This is fixed by adding filters.Add(new InitializeSimpleMembershipAttribute()); to the Application_Start (FilterConfig.RegisterGlobalFilters for MVC 4).
-
PasswordSalt: I saw that the PasswordSalt column is empty. I then found this question that lead to this answer.
“The good news is that appearances can be deceptive, and that the SimpleMembershipProvider does indeed salt hashes. In fact, the SimpleMembershipProvider makes use of the little known Crypto Helper whose HashPassword method is of most interest in relation to this article…”
Scott Hanselman on Universal Providers was also useful if not somewhat out dated. Universal Providers and SimpleMembership are not compatible.
http://www.asp.net/web-pages/tutorials/security/16-adding-security-and-membership – walk-through
http://blog.osbornm.com/archive/2010/07/21/using-simplemembership-with-asp.net-webpages.aspx - use SimpleMembership with Asp.Net
https://nuget.org/packages/MvcMembership/3.7.0 ~ Nuget package that has the roles administration built in, not sure if it works with SimpleMembership
http://msdn.microsoft.com/en-us/library/webmatrix.webdata.simplemembershipprovider%28v=vs.111%29.aspx
http://odetocode.com/Blogs/scott/archive/2012/10/01/build-your-own-membership-system-for-asp-net-mvc-part.aspx ~ K. Scott Allen is building his own membership system. This seems worth keeping an eye on.
http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/ ~ EF5 CodeFirst with SimpleMembership tutorial
http://blog.spontaneouspublicity.com/including-asp-net-simple-membership-tables-as-part-of-your-entity-framework-model ~another introduction
http://blog.spontaneouspublicity.com/including-asp-net-simple-membership-tables-as-part-of-your-entity-framework-model ~shows how to abstract out the WebSecurity calls for unit testing
We kept getting the ”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.” exception during development. We’d modify a page, then refresh and sometimes get an exception. It fixed it by going to the main index page and then back. The was happening because Simple Membership needs to be initialized in code before it can make any queries into the the membership. Here is what I did:
This probably won't make any sense, unless you've seen the code already and have the same setup as I do, but hopefully it will help out or you can let me know if I did it incorrectly.
I added a method on the Web.Filters.InitializeSimpleMembershipAttribute class that is setup in the FilterConfig file. I think this came with the new MVC 4.5 template??
so in FilterConfig public static void RegisterGlobalFilters(GlobalFilterCollection filters): I have a line:
// setup the Membership provider
filters.Add(new InitializeSimpleMembershipAttribute());
And in InitializeSimpleMembershipAttribute I added a static method:
public static void Initialize()
{
if (!WebSecurity.Initialized)
{
WebSecurity.InitializeDatabaseConnection(
"DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: false);
}
}
Then I call that in my base controller before I try to work with the membership system through the WebMatrix.WebData.WebSecurity object.
// don't do it if mocked with FakeItEasy
if (this.WebSecurity.GetType().FullName != "Castle.Proxies.ObjectProxy")
{
InitializeSimpleMembershipAttribute.SimpleMembershipInitializer.Initialize();
}