.Net Membership Provider not catching email duplications - asp.net-mvc

I am relatively new to .Net/MVC3, and am working on a C#, MVC3, EF4 application that makes use of the default membership provider. From my reading, it should automatically catch duplicate emails, but it does not seem to do so, and I'm not certain why. What I really need to figure out is where to look to see if the right pieces are in place, and for the reason(s) why it might not do that validation check (it seems to do most/all of the others, like duplicate user names, or invalid password formats, etc. with only duplicate email not getting caught.)
Customizations include adding new users to a specific role, and redirecting to a 'first time' welcome page.
Here is the code:
// POST: /Account/Register
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
Roles.AddUserToRole(model.UserName, "Registered");
//return RedirectToAction("Index", "Home");
return RedirectToAction("Acceptance", "Account");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Here are the (untouched) validation methods:
#region Status Codes
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// See http://go.microsoft.com/fwlink/?LinkID=177550 for
// a full list of status codes.
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
return "User name already exists. Please enter a different user name.";
case MembershipCreateStatus.DuplicateEmail:
return "A user name for that e-mail address already exists. Please enter a different e-mail address.";
etc.

By default, only usernames must be unique. If you want unique email addresses as well, then you must set that in the Web.config entry for the MembershipProvider.
something like
<membership>
<providers>
<add name="AspNetSqlMembershipProvider" [...] requiresUniqueEmail="true" [...] />
</providers>
</membership>

The only thing that comes to mind is making sure that model.Email has a value. Other thing you can check is look at the default membership provider tables in SQL server to check which values are being stored.

Related

MVC only alow admin login

I am working on a MVC5 EF6 database in Visual Studio 2013. The database is using Individual User Accounts for authentication with user roles.
In IIS it is possible to get the entire site down (e.g. for maintenance) by putting the app_offline.htm file. After I have updated the site and maybe migrated the database, as administrator I would like to perform some tests on the production machine before allowing all other users to login.
Is there a simple way to lockout all users except administrator from logging in until the administrator allows them to login ?
It would be nice if a similar construction can be used as the app_offline.htm file.
Below the code of the login method.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I can imagine it would be possible to add some code in case Success to logout the user (if not memeber of administrators) and redirect to a special "logins temporary disabled" page. However, there might be better (or builtin) alternatives.
Edit:
I tried the filtes and they do not work as I expected. However, I will keep filters in mind to test with if I have some more time to do so.
For now I use the following code in the login method
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationUser au = UserManager.FindByName(model.UserName);
// If user is found and not member of administrators
if (au != null && !UserManager.IsInRole(au.Id, "Administrator"))
{
// Check if the adminmode file exisits in the root. If so, redirect to it
String adminModeFilePath = System.IO.Path.Combine(HttpRuntime.AppDomainAppPath, "app_adminmode.htm");
if (System.IO.File.Exists(adminModeFilePath))
{
return new FilePathResult(adminModeFilePath, MediaTypeNames.Text.Html);
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
For "Administrator" and "app_adminmode.htm" I actually use constants which are declared in a central place, but for SO completeness I use the string here. If the user trying to login exists and is not in role Administrator, I will check the file. If the file exists the user is not logged in, but redirected
Maybe using the Authorize Attribute in FilterConfig.cs:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
filters.Add(new AuthorizeAttribute{Roles = "Adminsitrator"});
}
else
{
filters.Add(new AuthorizeAttribute());
}
Edit: Thanks #gavin-coates be sure to Allow Anonymous on the login page or any page that should be accessible by un-uathenticated users.
[AllowAnonymous]
public ActionResult Login() {
// ...
}
Adding a check to see if the user is an admin sounds ok to me.
The only thing I would change though, is to add some kind of boolean flag to enable/disable the restrictions, and place this in your web.config file, so you don't need to modify your code and re-upload it to enable/disable access.
Add the following into your web.config, <appSettings> section:
<add key="SystemLive" value="true" />
Then within your login function, add:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
return View("logins_temporary_disabled_page");
}

MVC 4 SimpleMembership - Why WebSecurity.CurrentUserId -1 after login

I am trying to set a cookie upon login and having issues with getting the current user id after login. In the below, intUserId is -1 and WebSecurity.IsAuthenticated is false. Is this not the correct place to put this code? After this, it redirects to the home page...so not sure why this is not the correct place.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//TODO: set current company and facility based on those this user has access to
int intUserId = WebSecurity.GetUserId(User.Identity.Name);
int intUserId2 = WebSecurity.GetUserId(model.UserName);
UserSessionPreferences.CurrentCompanyId = 1;
UserSessionPreferences.CurrentFacilityId = 1;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Login only sets the Forms Authentication cookie.
The way asp.net authentication works is that it must read the cookie to set the authenticate the request, but since the cookie did not exist when the Login page was started, the framework doesn't know anything about the user.
Reload the page, and you will find the information is available.
FYI, this is nothing new with SimpleMembership or WebSecurity, this is the way Forms Authentication has always worked.

How to delete a SimpleMembership user?

In my ASP.NET MVC app using Forms Authentication (via SimpleMembership), how do I delete a user/account?
The WebSecurity class doesn't expose DeleteUser. On a lark, I tried:
WebSecurity.InitializeDatabaseConnection(
"MyDbConnection", "Users", "Id", "UserName", autoCreateTables: true);
new SimpleMembershipProvider().DeleteUser(userName, true);
but that complains that I haven't initialized the SimpleMembership provider. In any event, I would very much appreciate some sample code that shows how to delete a user. Thanks!
Bob
PussInBoots is absolutely correct, although this always throws a foreign key constraint violation for me if the deleted user has been added to any roles. I'm sure this was inferred by PussInBoots' "//TODO: Add delete logic here" comment, but I will typically clean up role memberships first like this:
[HttpPost]
public ActionResult Delete(string userName, FormCollection collection)
{
try
{
// TODO: Add delete logic here
if (Roles.GetRolesForUser(userName).Count() > 0)
{
Roles.RemoveUserFromRoles(userName, Roles.GetRolesForUser(userName));
}
((SimpleMembershipProvider)Membership.Provider).DeleteAccount(userName); // deletes record from webpages_Membership table
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true); // deletes record from UserProfile table
return RedirectToAction("Index");
}
catch
{
return View(userName);
}
}
You probably need something like this:
//
// GET: /Members/Delete?userName=someuser
public ActionResult Delete(string userName)
{
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == userName);
return View(user);
}
//
// POST: /Members/Delete?userName=someuser
[HttpPost]
public ActionResult Delete(string userName, FormCollection collection)
{
try
{
// TODO: Add delete logic here
((SimpleMembershipProvider)Membership.Provider).DeleteAccount(userName); // deletes record from webpages_Membership table
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true); // deletes record from UserProfile table
return RedirectToAction("Index");
}
catch
{
return View(userName);
}
}
What happens if you just do Membership.DeleteUser(username,true). You might get a little prompt for adding a using directive on Membership. If you have it configured properly, you shouldn't need to be creating new SimpleMembershipProvider instance.
If you create it on the fly like that, you'll need to set connections on that object and configure it programmatically(it has no clue about the connection you created above). Usually people do that in web.config, but if you created the app using the forms authentication template, then you should have that taken care of automatically.
Your provider my have this bug for which is discussed and solved here: Membership.DeleteUser is not deleting all related rows of the user
I was getting the exception System.NotSupportedException from Membership.DeleteUser when running my unit tests. The problem was the app.config had the "DefaultProvider" set to "ClientAuthenticationMembershipProvider", which as you can see here is "not used by this class".
The fix was to update my app.config to match my web.config and properly configure the default provider:
<membership>
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Crelate.Properties.Settings.DatabaseMembershipServicesConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
Hey just wanted to post this for anyone running into ObjectContext state issues after following PussInBoots example, because I had the same problem...
If you are accessing additional user data you will need to remove that user from the data context using:
context.Users.Remove(user);
Rather than:
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true);
This will keep you EF context up to date and remove the user from the DB.

.Net Redirect to User Details after Login / passing the GUID of a User

I am using MVC 3 .Net membership. It's very new to me, and I'm stuck at redirecting the user to their user details page once they have successfully logged in. Here's the Logon Controller. My User details action is expecting the GUID of the User to be passed to it, and I don't know how to do that. I've tried User.id? and Membershipt.GetUser() etc etc but I can't seem to get the GUID of the user that the User Details action needs. Any ideas? Thanks in advance
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Details", "User", new { id = User.??????});
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
All I find with a quick search is problems, rather than solutions or examples. While this doesn't mean anything in and of itself--I'm not familiar with the "AccountModel" and "LogOnModel" namespaces and the related methods. But I could still possibly help.
First question: Where is the GUID stored? Are you using a directory (AD/LDAP) or a database (SQL). The GUID is "in there", and if it is already accessible, wouldn't it be something like model.GUID. Your ValidateUser found the username, but it doesn't "know anything" about the GUID.
ADODB, Linq, and DirectorySearcher all have relatively simple ways of fetching a GUID when you've got a valid username and password, for example.

Unit Testing Sqlite Membership Provider in MVC app

I've created an MVC application and I've set up Roger Martin's sqlite Providers in place of the default Providers. I'm curious about how I would go about unit testing these.
Below is a stripped down method that has many validations, only one of which is still present. Among other things, I want to write tests that ensures one can't register if the username has been taken, and can register if the username is free (and other validations pass), etc.
I can see how unit tests could determine success or failure, but not failure for a specific reason, unless I check the output MembershipCreateStatus parameter but I'm not sure if there is a better way. Further, what I need to give for object providerUserKey? Any insight would be very helpful.
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
//some validations, and then:
MembershipUser u = GetUser(username, false);
if (u == null)
{
///register user
status = MembershipCreateStatus.Success;
return GetUser(username, false);
}
else
{
status = MembershipCreateStatus.DuplicateUserName;
}
return null;
}
Use the out MembershipCreateStatus status output variable to determine the reason of failure (duplicte username, invalid password, duplicate email, etc).
The value will be one of the following:
MembershipCreateStatus.Success
MembershipCreateStatus.DuplicateUserName
MembershipCreateStatus.DuplicateEmail
MembershipCreateStatus.InvalidPassword
MembershipCreateStatus.InvalidEmail
MembershipCreateStatus.InvalidAnswer
MembershipCreateStatus.InvalidQuestion
MembershipCreateStatus.InvalidUserName
MembershipCreateStatus.ProviderError
MembershipCreateStatus.UserRejected

Resources