I have really strange problem with ASP.NET Identity and EntityFramework.
I have a login form, from which I receive username and password.
Then I check if the user exist in the database.
After that I call the UserManager's method VerifyHashedPassword to verify that the user password from database and that from the form are the same. Everything is OK, but for some of the users in the database, the method give me result that the given password and the hashed password are not the same (but they actually are). I just can't figure out why for some of the users password verification fails.
Here's my code.
public async Task<User> FindUserAsync(string userName, string password)
{
User user;
if (password != null)
{
user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
user = await _userManager.FindByEmailAsync(userName);
}
var result = _userManager.PasswordHasher.VerifyHashedPassword(user.PasswordHash, password);
if (!(result == PasswordVerificationResult.Success))
{
return null;
}
}
return user;
}
Well, the problem was on my side.
The problem came when some user decide to change their password.
If user change their password, I first delete the password and then I add the new password to the database. Which was very dumb, because the transaction to the database with which I delete the password is committed successfully, but the transaction (for some reason) with which I add the new password was never done successfully.
Actually I used these two methods when a password is changed:
await _userManager.RemovePasswordAsync(appUser.Id);
await _userManager.AddPasswordAsync(appUser.Id, fbUser.Password);
Now I do that manually:
String hashedNewPassword = _userManager.PasswordHasher.HashPassword(fbUser.Password);
if (hashedNewPassword != null)
{
appUser.PasswordHash = hashedNewPassword;
_context.Entry(appUser).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
And this works for me.
You can do this using the UserManager as long as you are able to do it async, the non-async extension method was broken for me at least, sending a null to the hashedPassword argument in the PasswordHasher. To do it async:
await _userManager.ChangePasswordAsync(userId, oldPassword, newPassword);
Related
I am writing an ASP.NET program where I need to store the users password in the database. But I get a password mismatched when I Compare the password from the database with the user input password. Even if the users password is correct.
Password Hashing:
string PasswordSalt = Crypto.HashPassword(DateTime.Now.ToString());
string hashPassword = Crypto.HashPassword(formcollection["PassWord"]); //Hash User PassWord
user.PassWord = Crypto.HashPassword(PasswordSalt + hashPassword);//Add Salt to Password For Futher Security
user.PassWordSalt = PasswordSalt;
Password Verification:
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string checkpassword = ThisUser.PassWord;
//User Inputed password.
string password = user.PassWord;
if (password != null)
{
//Need to fix.
string encrypt_password = Crypto.HashPassword(password);
string salted_password = Crypto.HashPassword(ThisUser.PassWordSalt + encrypt_password);
//bool does_password_match = Crypto.VerifyHashedPassword(checkpassword, password);
if (checkpassword == salted_password)
{
//Check if the inputed password matches the password from the Database.
//Remember to give session based on the user_id.
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else
{
ModelState.AddModelError("PassWord", "Wrong Password, Please Enter Correct Password");
return View(user);
}
I've never used it, but based on the documentation...
Crypto.HashPassword adds the salt for you and returns a base-64 encoded string with all the details in it to verify the password. So, you do NOT need to add a salt yourself.
All you need to do is store the hash result (base64EncodedHash below) in the DB, and then use it with VerifyHashedPassword to authenticate later. E.g. make a unit test like so:
var base64EncodedHash = Crypto.HashPassword("password");
Assert.IsTrue( Crypto.VerifyHashedPassword( base64EncodedHash, "password" ) );
Assert.IsFalse( Crypto.VerifyHashedPassword( base64EncodedHash, "otherPass") );
https://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.verifyhashedpassword(v=vs.111).aspx
To translate this to your code:
user.PassWord = Crypto.HashPassword(formcollection["PassWord"]);
Then to verify (comments added for quirks I see):
//Why are you storing "email" in Session before user is validated??? Seems off.
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string userInputPassword = user.PassWord; //this should be coming from POST
if( ThisUser != null && Crypto.VerifyHashedPassword(ThisUser.PassWord, userInputPassword) ) {
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else {
ModelState.AddModelError("PassWord","Your username or password are incorrect");
return View(user);
}
Ideally, as I somewhat indicated by my change of your error text...you also want to give the user the same error message whether the username/email or password are wrong. Your code, as is, probably returns a different error if the email doesn't return an account, but you don't want to give that much info to brute-force attackers.
You also need to put in some brute-force checking so that if they attempt too many times with failures, block that IP address for X amount of time., etc.
And, as someone said...when it comes to security...until you're the
expert...it's best to use pre-existing code/frameworks to mitigate you
risks.
I'm having an issue with the ForgotPassword method for the base asp.net identity. When stepping through the code, the line var user = await UserManager.FindByNameAsync(model.Email); returns null, even though I have confirmed that the email address for the user exists in the aspnetusers table. I'm not sure why Visual Studio will not allow me to step into the FindByNameAsync method? Not sure what's going on here?
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account",
new { UserId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Reset Password",
"Please reset your password by clicking here: link");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
You are trying to find an user by an email address.
You should use UserManager.FindByEmailAsync
This usually happens when you create the user using some other method than CreateAsync in Microsoft.AspNetCore.Identity.UserManager. I had the same issue because I was creating the users directly through EF, not the referred method.
All FindBy methods should work properly using this approach.
I had a similar issue for the project based on ASP.NET Core 2.2. Maybe my solution will be useful for someone.
The user can change their UserName in the UserProfile component (by default, the UserName was the same as Email, i.e., user1#mail.com). If the user changed their Username in the profile from the default user1#mail.com to user1, then they could not log in using this new UserName, only Email.
The line below always returned NULL.
var user = await _userManager.FindByNameAsync(request.UserName);
After investigating the AspCore repository, I found FindByNameAsync method. I become suspicious about NormalizeName line. And my current model for the UserProfile model had only UserName property, which was mapped later using Automapper and saved to the database. So I added computed NormalizedUserName property and also mapped it with Automapper (_mapper.Map(UserProfileModel, dbUser);) and saved it to the database.
public string NormalizedUserName
{
get
{
return UserName.ToUpper().Normalize(); // `UserManager` UserFindByNameAsync method is using `normalizedName` = `NormalizedUserName` from Users table (for some reason UPPERCASE, maybe SQL performance), otherwise we will always get NULL
}
}
Changes mentioned above solved my issue for NULL when using the FindByNameAsync method.
This can happen when the User table has a Query Filter applied to it and the filter criteria is not met.
I'm working on ASP.NET MVC5 app based around Parse.com framework.
Since i can't use Parse login method i had to use method posted here to work around its limitations: Parse.com multiple users issue
Here is my login method(just minor changes):
public async Task<ActionResult> Login(AccountModel model) //no returnUrl string
{
ParseUser user;
try
{
user = await ParseUser.LogInAsync(model.UserName, model.Password);//login parse user
}
catch (Exception e)
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
//making setAuthCookie get parse object id instead of username
FormsAuthentication.SetAuthCookie(user.ObjectId, model.RememberMe);
ParseUser.LogOut(); //log out parse user
return RedirectToAction("index", "home"); //Redirect to Action
}
So basically i (parse)login user, set AuthCookie to it's object id and then (parse)logoff user. That way i can have multiple users logged in.Out of SetAuthCookie i can get users id now.
However i'd like to display some extra user data(like user adress, Type, Name, LastName) that is on parse.com cloud. So i figured i will just write a method that will get this data by using currently authenticated userID, fill my AccountModel class object with data and then pass it to views. This is a loose idea of how it'd look like(i know syntax is probably wrong, i don't have access to my Visual studio right now):
UserData model:
public async Task<AccountModel> GetUserData()
{
AccountModel userData = new AccountModel();
ParseObject user;
ParseQuery<ParseObject> query = ParseObject.GetQuery("_User");
try
{
//i can't remember how to get authenticated user identity
user = await query.GetAsync(AuthenticatedUser.Identity());
}
catch(Exception e)
{
//code to handle exception
}
userData.Name = user.Get<string>("Name");
userData.Lastname = user.Get<string>("Lastname");
userData.Adress = user.Get<string>("Adress");
return userData; //it will probably throw an error
}
Controller:
public ActionResult Index()
{
UserData model = new UserData();
return View(model.GetUserData());
}
So now it will probably throw an error(can't return T from Task< T >) and i have no idea how to fix this, so i can get currently logged in user data.
I have nav bar on my site where user name and last name is displayed, so i have to somehow get currently logged in user data every time page is displayed. Is there any work around/easier way to achieve this?
You fix this by making your Action asynchronous as well:
public async Task<ActionResult> Index()
{
UserData model = new UserData();
return View(await model.GetUserData());
}
Async goes "all the way". This means that once you have an asynchronous method that needs to be awaited, it will usually cause most (if not all) of your stack-trace to be asynchronous as well.
Side note:
Once should stick to .NET conventions and mark async methods with the XXXAsync postfix, so your method should actually be named GetUserDataAsync.
I'm working on an intranet, I've just added a feature on the user's profile to change his password.
As you can see with the following controller :
[HttpPost]
public ActionResult ChangePassword(Employee objToEdit, FormCollection form, LocalPasswordModel model) // Find how to obtain "OldPassword" from AccountModel
{
objToEdit.Login = User.Identity.Name;
string name = objToEdit.FirstName;
string pwd = form["NewPassword"];
string confirm = form["ConfirmPassword"];
if (_service.Edit_password(objToEdit, pwd, confirm)) // Checks if NewPassword and ConfirmPassword are the same, and does some syntax checking
{
bool changePasswordSucceeded;
try
{
changePasswordSucceeded = WebSecurity.ResetPassword(WebSecurity.GeneratePasswordResetToken(objToEdit.Login), pwd); // Seems to work
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Index", new { Message = CRAWebSiteMVC.Controllers.AccountController.ManageMessageId.ChangePasswordSuccess });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
return new RedirectResult(Url.Action("Index"));
}
return View();
}
So far, the user just needs to input a New password and a confirmation password. I wish to add a "Enter your current Password" feature but I can't find a way to retrieve the user's current password !
The user profile DB does not contain a Password column anymore and I use Form authentication if that's of any help.
EDIT: Thank you for your help, to solve my problem I simply replaced the ResetPassword line by the following :
changePasswordSucceeded = WebSecurity.ChangePassword(objToEdit.Login, current, pwd);
If it fails, it directly displays the error message that the current password is wrong.
You can't !
That's actually a security feature. You should never store a password in plain text.
The good thing is, you don't need to do the comparison yourself:
Instead, use something like ValidateUser to let the Membership Provider validate the provided password. Behind the scenes, this method will hash the password and compare it with the hashed version contained in the database.
EDIT:
Also, note that since you are using the WebSecurity class, there is a method, ChangePassword that accepts the current password. It seems that method will check the current password matches the specified currentPassword parameter. Maybe you should use this one instead of ResetPassword
I'm using SimpleMembership in an MVC4 app and need the ability for a user to change their own username.
I have the functionality working, so when a user changes their username it works. However when invoking things like Roles.IsUserInrole() then it fails as User.Identity.Name is set to what they logged in as, not the new value. That value no longer exists in the database, as they have changed their name.
I can't see a method to update the logged in user context with a username. For the most part I can store the users ID in session and retrieve it when doing queries, but I'm using the Roles method to display data in a view which fails.
Any ideas?
Thanks!
This is my current (working) solution:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AccountEditModel model)
{
if (ModelState.IsValid)
{
if (HasSensitiveInformationChanged(model)) // model.EmailAddress.ToLower() != User.Identity.Name.ToLower()
{
if (Membership.ValidateUser(User.Identity.Name, model.Password)) // && WebSecurity.IsCurrentUser(User.Identity.Name)) //redundant?
{
using (UsersContext db = new UsersContext())
{
UserProfile user = db.UserProfiles.FirstOrDefault(u => u.EmailAddress.ToLower() == User.Identity.Name.ToLower());
if (user != null)
{
user.EmailAddress = model.EmailAddress;
db.SaveChanges();
WebSecurity.Logout();
WebSecurity.Login(model.EmailAddress, model.Password);
return RedirectToAction("Index", "Search");
}
else
{
ModelState.AddModelError("", "Could not find user. Please try logging in again.");
}
}
}
else
{
ModelState.AddModelError("","Could not change email address. Please verify your password and try again.");
}
}
else
{
//no change
return RedirectToAction("Index", "Search");
}
}
return View("Index", model);
}
I didn't think you were able to change the username field (would be nice if you could show me how you achieved that).
The only solution I see is that you force the user to log off after changing the username, so when they log back in they will have the correct User.Identity.Name.