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
Related
I am new to .Net and I am building a web application where I want to create a session when a user logs in or registers and confirms registration. For logging in I have a razor page: Login where it simply prompts the user for their Username and Password which should be in my sql server and validated. Once it checks that the valid user flag is correct I want to create the session and send the user to Index for now. Then in Index I want to see if the session has been created and I want to read the value within the session and display it onto the Index view to confirm that the user info has been captured.
My Login.cs file looks like this:
public ActionResult OnPost(string UserName, string Password)
{
if (!ModelState.IsValid)
{
return Page();
}
if(accountService.ValidateUserLogin(UserName, Password) == 1)
{
string Email = accountService.UsernameGetEmail(UserName);
HttpContext.Session.SetString(Email, TempData["email"].ToString());
return RedirectToPage("/Index");
}
else
{
//report error to user
return Page();
}
}
This is where I try to capture the session info in my Index.cs page:
public void OnGet()
{
if (HttpContext.Session.Id != null)
{
SessionEmail = HttpContext.Session.GetString("Email");
ViewData["SessionEmail"] = SessionEmail;
}
}
This is the piece of html where I try to display that SessionEmail onto the view but it is null upon initialization of SessionEmail inside the index.cs file.
<div>
#{
if( ViewData["SessionEmail"] != null)
{
<p> Hello! #ViewData["SessionEmail"].ToString() </p>
}
}
</div>
Hopefully you can point out my mistakes here and how I try to set up a session. After I am able to set up a session I am going to edit my layout's navbar so that it will change if a user is signed in or not so maybe if you can point out how I can go about this with sessions that would be great, perhaps: httpcontext.session.IsAvailable? Also, I do have addSession and useSession in my startup as well.
Try this:
HttpContext.Session.SetString("Email", Email);
The reason is that you're getting the user's email address here:
string Email = accountService.UsernameGetEmail(UserName);
Then you're trying to save it to the session using the SetString extension method. That method takes a key as the first argument and the value as the second.
Later, you are calling the GetString extension method with a key of "Email":
SessionEmail = HttpContext.Session.GetString("Email");
So you need to set the value with that same key:
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 am using the following code to generate a reset Token
var token = WebSecurity.GeneratePasswordResetToken(user);
it worked the first time, in that it updated the webpages_Membership table, and updated the following fields,
PasswordVerificationToken
PasswordVerificationTokenExpirationDate
but when I run the above code again, the token returned is the same, and nothing is updated.
how does it work?
The token will only refresh once it has been used with WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);. If you do not complete the full password reset process, the same token will always be generated for that specific user.
If the password is not reset within 24 hours (default), then the above method will return false. If you want to override the default password expiration, you can add the optional parameter tokenExpirationInMinutesFromNow when calling WebSecurity.GeneratePasswordResetToken :
public static string GeneratePasswordResetToken(
string userName,
int tokenExpirationInMinutesFromNow
)
If you want to catch a bad password change attempt, just wrap WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword); in a try catch block, and you'll be able to display an error to the user :
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordModel model)
{
if (ModelState.IsValid)
{
try
{
//Reset password using the reset token and the new password
WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);
//Redirect to the home account page.
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, LocalizedText.Account_Reset_Password_Error);
}
}
//Something bad happen, notify the user
return View(model);
}
I won't worry to much about catching a specific exception here, because the fix will be the same either way - they'll need to generate another password reset token.
Microsoft is coming up with a new Membership system called ASP.NET Identity (also the default in ASP.NET MVC 5). I found the sample project, but this is not implemented a password reset.
On password reset topic just found this Article: Implementing User Confirmation and Password Reset with One ASP.NET Identity – Pain or Pleasure, not help for me, because do not use the built-in password recovery.
As I was looking at the options, as I think we need to generate a reset token, which I will send to the user. The user can set then the new password using the token, overwriting the old one.
I found the IdentityManager.Passwords.GenerateResetPasswordToken / IdentityManager.Passwords.GenerateResetPasswordTokenAsync(string tokenId, string userName, validUntilUtc), but I could not figure out what it might mean the tokenId parameter.
How do I implement the Password Reset in ASP.NET with MVC 5.0?
I get it: The tokenid is a freely chosen identity, which identifies a password option. For example,
1. looks like the password recovery process, step 1
(it is based on: https://stackoverflow.com/a/698879/208922)
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPassword(
ResetPasswordViewModel rpvm)
{
string message = null;
//the token is valid for one day
var until = DateTime.Now.AddDays(1);
//We find the user, as the token can not generate the e-mail address,
//but the name should be.
var db = new Context();
var user = db.Users.SingleOrDefault(x=>x.Email == rpvm.Email);
var token = new StringBuilder();
//Prepare a 10-character random text
using (RNGCryptoServiceProvider
rngCsp = new RNGCryptoServiceProvider())
{
var data = new byte[4];
for (int i = 0; i < 10; i++)
{
//filled with an array of random numbers
rngCsp.GetBytes(data);
//this is converted into a character from A to Z
var randomchar = Convert.ToChar(
//produce a random number
//between 0 and 25
BitConverter.ToUInt32(data, 0) % 26
//Convert.ToInt32('A')==65
+ 65
);
token.Append(randomchar);
}
}
//This will be the password change identifier
//that the user will be sent out
var tokenid = token.ToString();
if (null!=user)
{
//Generating a token
var result = await IdentityManager
.Passwords
.GenerateResetPasswordTokenAsync(
tokenid,
user.UserName,
until
);
if (result.Success)
{
//send the email
...
}
}
message =
"We have sent a password reset request if the email is verified.";
return RedirectToAction(
MVC.Account.ResetPasswordWithToken(
token: string.Empty,
message: message
)
);
}
2 And then when the user enters the token and the new password:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPasswordWithToken(
ResetPasswordWithTokenViewModel
rpwtvm
)
{
if (ModelState.IsValid)
{
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(
rpwtvm.Token,
rpwtvm.Password
);
if (result.Success)
{
message = "the password has been reset.";
return RedirectToAction(
MVC.Account.ResetPasswordCompleted(message: message)
);
}
else
{
AddErrors(result);
}
}
return View(MVC.Account.ResetPasswordWithToken(rpwtvm));
}
Skeleton proposal to sample project on github, if anyone needs it may be tested.The E-mail sending not yet written, possibly with the addition soon.
Seems like a lot of trouble...
What advantage does the above give over:
the user clicking a 'Recover Account' link
this sends an 64 byte encoded string of a datetime ticks value (call it psuedo-hash) in an email
click the link back in the email to a controller/action route that
matches email and it's source server to psuedo-hash, decrypts the psuedo-hash, validates the time since sent and
offers a View for the user to set a new password
with a valid password, the code removes the old user password and assigns the new.
Once complete, successful or not, delete the psuedo-hash.
With this flow, at no time do you EVER send a password out of your domain.
Please, anyone, prove to me how this is any less secure.
I created an App with ASP.NET MVC 1.0 and wish to use a custom method (for admins) to create a user. I took the Register method (in the Account controller) and renamed it to Create. I then commented out the line FormsAuth.SignIn(userName, false); to avoid the newly created user to sign in.
When I complete the create user form, the user gets added fine, but he also gets signed in. Now both me and the new user are signed in. I know this because my ListUsers page tests for user.IsOnline
UPDATE (2009-07-15 14:40): I have been doing some Google-ing and found that User.IsOnline is not very reliable due to the stateless HTTP protocol. Note: if I go to the UserDetails page (which is populated using MembershipUserAndRolesViewData) the Last Login shows as NULL. But my ListUsers page shows a login date...???
public class AccountController : Controller
{
// ...
[Authorize(Roles = "Administrator")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string userName, string email, string password, string confirmPassword)
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
if (ValidateRegistration(userName, email, password, confirmPassword))
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);
if (createStatus == MembershipCreateStatus.Success)
{
//FormsAuth.SignIn(userName, false); // createPersistentCookie
return RedirectToAction("ListUsers", "Account");
}
else
{
ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View();
}
}
Checking http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.isonline.aspx mentions this:
A user is considered online if the current date and time minus the UserIsOnlineTimeWindow property value is earlier than the LastActivityDate for the user.
The LastActivityDate for a user is updated to the current date and time by the CreateUser, UpdateUser and ValidateUser methods, and can be updated by some of the overloads of the GetUser method.
This page http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.lastactivitydate.aspx also says this:
The LastActivityDate for a user is updated to the current date and time by the CreateUser and ValidateUser methods, and can be updated by some overloads of the GetUser method. You can use the UpdateUser method to set the LastActivityDate property to a specific date and time.
So it seems that when you create a new account, this is considered as being "Online".
A workaround could be to modify the default CreateUser in the AccountMembershipService class to reset the date when you create an account:
public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
MembershipCreateStatus status;
MembershipUser user = _provider.CreateUser(userName, password, email, null, null, true, null, out status);
user.LastActivityDate = DateTime.MinValue; //set the LastActivityDate to a point far back in the past
_provider.UpdateUser(user); //update the user to the MembershipProvider
return status;
}
There must be something wrong with a code you didn't show. If you create a new ASP.NET MVC project using the default VS template and then comment out the line FormsAuth.SignIn(userName, false) the user is not signed in as expected.
It's a bit of a hack, but I suppose you could log them out before redirecting them.
FormsAuth.SignOut();
return RedirectToAction("Index", "Home");