SimpleMembership.Mvc3 – 1.1 Published
If you’re unfamiliar with SimpleMembership.Mvc3, please see this post.
Update: May 18, 2011 – Version 1.1 published to NuGet.org
This includes:
- Minor fix to WebSecurityService to address bug reported by Phil Haack.
- Added assembly reference to web.config.transform to reference WebMatrix.Data. Some users have reported exceptions being thrown trying to reference that namespace.
New SimpleMembershipAccountController which can be used as a drop-in replacement for the default AccountController in new Mvc projects. This controller uses WebSecurityService instead of the FormsAuthenticationService and MembershipService for authentication and membership.The SimpleMembershipAccountController has been moved to a new sample package: SimpleMembership.Mvc3.Sample It can be used as a drop-in replacement for the default AccountController in the Mvc3 Visual Studio project template. It uses the WebSecurityService from SimpleMembership.Mvc3 instead of the FormsAuthenticationService and MembershipService which come in the default project template.

about 2 years ago
Having some problems:
class LogonModel not found. I didn’t see any classes in the Models folder.
about 2 years ago
@Trevor,
Sorry, you first need the default Mvc3 project that includes the LogOnModel, RegisterModel, etc.
I went ahead and removed the SimpleMembershipAccountController from the NuGet package (now you’ll see version 1.2 on nuget.org) until I figure out a better way to deliver this without breaking things at compile-time.
Let me know if you still have any issues.
about 2 years ago
Sorry, I understand now, I have to add it to an existing MVC site with membership. I was adding it to an empty MVC site.
about 2 years ago
Any ideas for getting this to work in conjunction with DotNetOpenAuth?
about 2 years ago
@Trevor,
To keep things clean, I moved the SimpleMembershipAccountController to a new package: SimpleMembership.Mvc3.Sample
If you install the sample package, you’ll automatically get the SimpleMembership package.
Let me know if you run into anything else.
about 2 years ago
This is a great API, way better than the other membership providers.
Could you post a demo or walk-through on how to use this in MVC apps? It would help us guys new to MVC/programming out a lot!
about 1 year ago
I want to use SimpleMembership in an MVC 3 website, but I’m getting the following error:
System.ArgumentException was unhandled by user code. Unable to find the requested .Net Framework Data Provider. It may not be installed.
I’m using a SQL Server database, through Entity Framework. Here is my connection string:
I already use EF in other parts of the application, so this connection string is correct and I have connection to the database. Can you please help?
about 1 year ago
@James,
I’ve posted a step-by-step walk-through on how to get this working with an MVC3 app. Sample code is included.
Let me know if you have any questions.
Adam
about 1 year ago
A couple items.
1. There’s still a bug that keeps the Token from being returned. In AccountController.cs, line 89, could read: var token = WebSecurityService.CreateUserAndAccount(model.UserName, model.Password, requireConfirmationToken: requireEmailConfirmation); – otherwise the value get placed into the propertyValues parameter.
2. Is there a lot missing from the sample project? E.g. Where is the code for sending the confirmation email? How about the confirmation and password reset pages, etc? Are those missing ?
about 1 year ago
@Mike
Good catch. You’re right that line 89 in AccountController should probably read: WebSecurityService.CreateUserAndAccount(model.UserName, model.Password, requireConfirmationToken: requireEmailConfirmation); or I need to add a CreateUserAndAccount method to IWebSecurityService/WebSecurityService that doesn’t expect the propertyValues param.
Also, the SimpleMembership.Mvc3.Sample package is just meant to give you a quick, pre-wired account controller (small sample) that you can drop into an existing project or use with a new project, not necessarily a fully-functional example with sending the confirmation email, password reset, etc. The main reason I didn’t include those features in the package is that some folks (myself included) were wanting to add this to an existing project and I wanted to minimize the amount of existing code/views I would be stepping on. Also, some people may be using external services for sending email and I didn’t want to make any assumptions about how they might be sending email, etc. by introducing that code. So, in short, I opted for less code (though not a full example site) as a means of a starting point to get people going.
However, you’re not the first person to bring up the need for a “template” site using MVC3, SimpleMembership, fully-scaffolded out with some code for user registration, email confirmation, password reset, etc.
I’m considering either creating a new package to provide just that or trying to make the existing sample package smart enough (maybe using PowerShell) that it only adds stuff if you don’t already have that.
In any case, I’ll update the Sample package to fix the issue you noted in #1.
In the meantime, let me know if you have any other questions.
Thanks for your feedback.
Adam
about 1 year ago
@Mike,
I just pushed the fix to the SimpleMembership.Mvc3.Sample package.
Check it out and let me know if you run into any other issues.
Adam
about 1 year ago
Thanks Adam – appreciate the quick response as well!
(If it were a debate
I’d argue for 2 points:
1) that the main reason a developer would look for a package like this is to handle confirmations and password resets. Otherwise, the ASP.NET membership is adequate.
2) your strategy (choosing an unlikely name) works well for avoiding stepping on existing stuff. I’d argue that it’s pretty unlikely you’ll be stepping on anyone’s existing code/views if you stick with it.
Mike.
about 1 year ago
Mike,
Thanks for the feedback. I think based on that and feedback from others, I’m going to incorporate the other pieces which I was holding back initially.
Look for them soon on NuGet.
Thanks again,
Adam
about 1 year ago
@Mike,
SimpleMembership.Mvc3.Sample 1.2 has been published to NuGet.
It adds account confirmation, forgot password, password reset, etc.
Same instructions apply as before. You need to remove the default AccountController and rename the SimpleMembershipAccountController.cs to AccountController.cs.
Let me know what you think.
Adam
about 1 year ago
@Adam,
This is awesome! Thanks so much. I think this will save folks a lot of time. All they have to do is find it and the NuGet package really makes it easy.
Honestly, I can’t believe that confirmation, password reset, etc. isn’t included with base Asp.Net membership provider. I haven’t seen any updates saying that will change anytime soon so this is a great solution.
about 1 year ago
@Adam,
If it’s OK, I’ll make (5) suggestions. I think one of them is important:
1) minor – in App_Start\SimpleMembershipMvc3.cs I changed the connectionStringName
– from: “Default”
– to: “ApplicationServices”
since VS adds ApplicationServices automatically when you create a new Internet Application.
2) minor – in Web.Config – in the “” section, I added two items:
1)
to change the default lagin page that WebMatrix users to when a users tries to access a page that requires authorization.
2)
3) minor – in Services\MessengerService.cs I added a throw so that I know when email send fails:
catch (Exception ex)
{
//Log exception
throw ex;
}
4) important – when registering, if the UserName already exists, it throws an exception. To avoid this I added some code in AccountController.cs to check if the UserName’s taken. I also save the email address in propertyValues for the next suggestion.
if (ModelState.IsValid)
{
// Check whether the user already exists
if (WebSecurityService.UserExists(model.UserName))
{
ModelState.AddModelError(“”, “Username is already in use.”);
}
else
{
// Attempt to register the user
var requireEmailConfirmation = true;
// Save the email address in PropertyValues
var token = WebSecurityService.CreateUserAndAccount(model.UserName, model.Password, propertyValues: new { Email = Request["email"] }, requireConfirmationToken: requireEmailConfirmation);
if (requireEmailConfirmation)
{
// Send email to user with confirmation token
string hostUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
string confirmationUrl = hostUrl + VirtualPathUtility.ToAbsolute(“~/Account/Confirm?confirmationCode=” + HttpUtility.UrlEncode(token));
var fromAddress = “yourEmailAddress@host.com”; // TODO: Edit fromAddress
var toAddress = model.Email;
var subject = “Thanks for registering but first you need to confirm your registration…”;
var body = string.Format(“Your confirmation code is: {0}. Visit {1} to activate your account.”, token, confirmationUrl);
// NOTE: This is just for sample purposes
// It’s generally a best practice to not send emails (or do anything on that could take a long time and potentially fail)
// on the same thread as the main site
// You should probably hand this off to a background MessageSender service by queueing the email, etc.
MessengerService.Send(fromAddress, toAddress, subject, body, true);
// Thank the user for registering and let them know an email is on its way
return RedirectToAction(“Thanks”, “Account”);
}
else
{
// Navigate back to the homepage and exit
WebSecurityService.Login(model.UserName, model.Password);
return RedirectToAction(“Index”, “Home”);
}
}
}
5) important*** – in ForgotPassword the user can enter any email address. This allows anyone who can guess a username to use ForgotPassword to reset the users password and essentially steal the users account.
So, I added some code to compare the email address they enter to the user entered when they created the account (saved in propertyValues above). If the 2 match, then the token is generated.
if (ModelState.IsValid)
{
var userID = WebSecurityService.GetUserId(model.UserName);
String SavedEmail = string.Empty;
if ( userID > -1 && WebSecurityService.IsConfirmed(model.UserName) )
{
using (var conn = new SqlConnection(WebConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString))
{
using (var cmd = new SqlCommand(“Select Email from users where ID = ” + userID.ToString(), conn))
{
conn.Open();
var ret = cmd.ExecuteScalar();
SavedEmail = (ret is DBNull) ? string.Empty : ret.ToString();
}
}
if (SavedEmail == model.Email)
{
resetToken = WebSecurityService.GeneratePasswordResetToken(model.UserName);
isValid = true;
}
}
if (isValid)
{
string hostUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
string resetUrl = hostUrl + VirtualPathUtility.ToAbsolute(“~/Account/PasswordReset?resetToken=” + HttpUtility.UrlEncode(resetToken));
var fromAddress = “yourEmailAddress@host.com”;
var toAddress = model.Email;
var subject = “Password reset request”;
var body = string.Format(“Use this password reset token to reset your password. The token is: {0}Visit {1} to reset your password.”, resetToken, resetUrl);
MessengerService.Send(fromAddress, toAddress, subject, body, true);
return RedirectToAction(“ForgotPasswordMessage”); // moved inside
}
}
about 1 year ago
Sorry, I should have said two are important.
about 1 year ago
Just out of curiosity, why use WebSecurity rather than SimpleMembershipProvider?
I see that WebSecurity includes InitializeDatabaseConnection, but SimpleMembershipProvider seems to be a superset of WebSecurity.
about 1 year ago
@Mike,
Even with the built-in ASP.NET Membership Providers, you never use the membership provider directly because that would couple you to the specific implementation (provider). The static Membership class provides the facade (interface) to the provider so that providers can be swapped out via configuration. WebSecurity does the same for SimpleMembershipProvider. I think Microsoft created this class because they couldn’t alter the existing Membership class because they would essentially be changing the interface which would break existing membership code. So, they created WebSecurity. By using WebSecurity (or in this case IWebSecurityService), you could still create your own membership provider (for instance backed by Azure Table storage) or a dummy one just for testing and still use the interface/method signatures provided by WebSecurity.
Hope that helps.
Adam
about 1 year ago
@Mike,
Thanks for the suggestions.
I’ll take a look at these and try to incorporate them soon.
Adam
about 1 year ago
@Adam – thanks again.
>> you never use the membership provider directly because that would couple you to the specific implementation.
Good point – I should have realized before I posted.
The reason I asked was that I was looking for a way to avoid letting bad guys take over someone elses account by entering their own email address in the password reset screen (so that the password reset token would be sent to their email address rather than the owners).
SimpleMembershipProvider has GetUserNameByEmail (which I assume retrieves the email address that was originally entered by the owner – provided we save it in propertyValues). The code I posted above uses ADO.NET to retrieve the address.
Kinda seems like something’s missing in WebSecurity – or maybe I’m missing something or looking at it from a different viewpoint…
about 1 year ago
@Mike,
The way that I prevent the scenario you described is that I present the user with a Forgot Password or Password Reset page where they enter their email address. I generate a password reset token (with an appropriate expiration) and then construct a unique URL link which I then email to that email address (I don’t provide this link to the user). If the user owns that email address, they will receive an email with a personalized link (with the password reset token) and they will be able to reset their password. If they are not the real user, then the account is not affected because the account is not affected by the forgot password functionality. Only when the user comes back to the site with a valid reset token, can they actually reset their password.
Hope that helps.
Let me know if you have any questions.
Adam
about 1 year ago
@Adam,
But all you have to do to steal someone elses account is go to the “Forgot Password” page:
1. Enter *** THEIR Username ***
2. Enter an email address *** YOU OWN ***
I just tried it – I created a new account:
1. Username: “MikeSmith”
2. Email: “MikeSmith@whitehat.com”
Then, I went to the “Forgot Password” page and entered:
1. Username: “MikeSmith”
2. Email “JoeCool@blackhat.com”
The password reset token for MikeSmith’s account was sent to JoeCool@blackhat.com.
JoeCool clicked the unique URL link and entered a new password for MikeSmith’s account. Now JoeCool is logged into MikeSmith’s account and MikeSmith can no longer log in since the password on his account has been changed by Joe.
The email address where the “password reset token” is sent isn’t validated at all. So the token can be sent anywhere.
Mike.
about 1 year ago
@Mike,
You’re right. The way it currently works, you could do what you illustrated.
The ForgotPassword code and some other recent functionality actually came from another user’s pull request, but I should have looked at it more closely.
So, a couple of things:
I think this is because Microsoft assumes that most users use email address as the username nowadays. However, in the case where you have username and email, you have to do exactly what you showed by implementing a user lookup by email address method. Microsoft also doesn’t know what database column you are using for email address if you don’t make it the username (you could be using “Email”, “EmailAddress”, etc.). Remember, WebSecurity.InitializeDatabaseConnection() includes a parameter for the user ID (identifier of the user) typically an int or Guid and a param for the username. It doesn’t distinguish between username and email, so SimpleMembershipProvider can’t reliably implement GetUserNameBymail()
Thanks for your feedback and patience.
Adam
about 1 year ago
@Adam,
My plan is to use email address as the username to avoid the problem. That may be the best solution for the sample as well (for the reasons you mention).
Thanks for the heads-up on GetUserNameByEmail().
Keep up the great work!
about 1 year ago
@Adam,
Did you have a chance to update the sample?
about 1 year ago
Also checking back in regarding the sample update? I am also very concerned about the lack of email/username validation for forgotten password. Too easy for someone to mess that up. I almost didn’t see it.
And also, if you could make it work against the System.Web.Providers also/instead of that would be great as it would make things really flexible for implementing all types of sql server. I am stumped but it looks like it wouldn’t be too difficult to do?
http://www.hanselman.com/blog/IntroducingSystemWebProvidersASPNETUniversalProvidersForSessionMembershipRolesAndUserProfileOnSQLCompactAndSQLAzure.aspx
about 1 year ago
@Drewid/@Mike,
Haven’t updated it yet to remove username from the forgot password page. I plan to have that in place next week.
@Drewid,
As for System.Web.Providers, the goal behind that project is to simply extend support to all versions of SQL Server for the built-in membership providers. As you may recall, the original ASP.NET SQL Membership Provider relied on Stored Procedures (which don’t work with SQL Compact) and it wasn’t compatible with SQL Azure. SimpleMembership, on the other hand, is its own new provider (born out of ASP.NET WebPages) and works out of the box with SQL Compact, SQL Express, SQL Server and SQL Azure. So, there really isn’t anything that needs to change – it should already work.
Hope that helps!
Adam
about 1 year ago
Hi Adam,
I found this blog quite by accident and it’s helped a lot.
I couldn’t find the implementation for GetAllUsers in WebSecurityService.cs (or the interface). Do you have a code snippet or similar on how this could be implemented.
Regards,
Jules
about 1 year ago
Hi Adam,
Is there a method for retrieving all roles as well?
Regards,
Jules
about 1 year ago
@Jules,
I haven’t implemented every method in WebSecurity. I have a couple of updates planned for this package and will try to incorporate this request into my next series of updates. I hope to have these complete in January.
Please let me know if you have any other questions.