The issue I am experiencing is I am modifying my model in the post action, instead of having it filled in through the form (the fields are a password hash and password salt) for obvious reasons. When post to the action, obviously the password hash and salt are computed values not user entered. The issue is, if I generate them and assign the values to my posted customer model, model state still says they are required even though the properties have values. See code below. This is my register action.
[HttpGet]
public ActionResult Register()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Customer customer)
{
var password = Request.Form.Get("password");
var ConfirmPassword = Request.Form.Get("confirmpassword");
if ((password != null && ConfirmPassword != null) && (!string.IsNullOrWhiteSpace(password)
&& !string.IsNullOrWhiteSpace(ConfirmPassword)) && password == ConfirmPassword)
{
//generate a password salt
var passwordsalt = Models.Helpers.PasswordHasher.GetSalt();
//convert it into a string that can be used again by calling the Convert.FromBase64String(string); function on what will be stored
customer.PasswordSalt = Convert.ToBase64String(passwordsalt);
//compute the password hash here and store it in the customer
customer.PasswordHash = Models.Helpers.PasswordHasher.ComputeHash(password, "SHA256", passwordsalt);
}
else if (!Models.Helpers.ValidationLibrary.ValidatePasswordRequirements(password))
{
ModelState.AddModelError("", "Password must be 8 characters long, have at least one number or symbol");
}
else
{
ModelState.AddModelError("", "Password and confirm password do not match");
}
if (ModelState.IsValid)
{
//db.Customers.Add(customer);
//db.SaveChanges();
UserRegistration regularUser = new UserRegistration();
regularUser.customer = customer;
regularUser.role = new XREF_CustomerRole { Role_ID = 3, Customer_ID = customer.Customer_ID };
Models.Helpers.Helper.createUser(regularUser);
return Login(new UserLogin { Email = customer.Email, Password = customer.PasswordHash, RememberMe = false });
}
return View(customer); ;
}
Here is a screenshot of the values.
And here is a screenshot of the values in model state
Along with their corresponding keys
That's normal and it is how HTML helpers work. They first use the value of the POST request and after that the value in the model. This means that even if you modify the value of the model in your controller action if there is the same variable in the POST request your modification will be ignored and the POSTed value will be used.
ASP.Net MVC Html.HiddenFor with wrong value
Related
I have a Model that produces 2 partial views.
One partial view contains a form to change your username
The other contains a form to change your password
Both forms reach back to a POST method in the UserController. Each section, independently works fine. However, if I change the username and then try to change the password, the Auth Context contains the old username and I encounter an error.
The logic goes a little like this...
Change Username
Controller
public async Task<ActionResult> ChangeUsername(ChangeUsernameViewModel model) {
string oldUsername = model.OldUsername;
string newUsername = model.NewUsername;
User user = await this.UserService.GetUserById(this.Authorization.UserId);
if (user != null)
{
// Update username in DB
User user = await this.UserService.ChangeUsername(user, newUsername);
// Update cookie
this._owinContext.Authentication.SignIn(this.Authorization.ToClaimsIdentity());
// Update ViewModel
model.OldUsername = newUsername;
model.NewUsername = string.Empty();
}
return View(model);
}
Service
public async Task<User> ChangeUsername(User user, string newUsername) {
// Blah blah blah... Code to update user with new username
// and save changes to DB which is then followed by:
// Change claim in Auth Context
this._authorization.RemoveValue(MyClaimType.Username);
this._authorization.AddValue(MyClaimType.Username, newUsername);
// At this point, I can see that the Auth Context
// has been updated with the new username.
return user;
}
Change Password
Controller
public async Task<ActionResult> ChangePassword(ChangePasswordViewModel model) {
string oldPassword = model.oldPassword;
string newPassword = model.newPassword;
User user = await UserService.GetUserByLogin(this.Authorization.Username, oldPassword);
// this is where the failure occurs, so I won't
// bother writing out the rest.
// this.Authorization.Username is equal to "oldUsername"
// that we saw in the ChangeUsername method.
}
Does this._authorization in the service not carry back over to this.Authorization the controller?
Is this.Authorization different for each partial view for some reason?
Clear/Reset/logout the user session, Reload the session data info with the new data supplied by the user or from db
So, I have a problem I am curious about. I have a UserAccountViewModel that I am reusing for create account view and Edit Account view. So that I can use one view and one viewmodel for my code but different controller actions.
On Create Account, UserName, Password and Confirm Password are all required fields. However, on Edit Account, those fields are not required fields (I hide those fields in my view on Edit Account).
The problem is when I do this:
[HttpPost]
public ActionResult EditUserAccount_Save(UserAccountViewModel editUserAccountViewModel)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
editUserAccountViewModel.UserName = UserSession.GetValue(StateNameEnum.UserName, StateNameEnum.UserName.ToString()) as string;
db_user user = new db_user();
user.Title = editUserAccountViewModel.Title;
user.FirstName = editUserAccountViewModel.FirstName;
user.LastName = editUserAccountViewModel.LastName;
user.PhoneNumber = editUserAccountViewModel.PhoneNumber;
user.AltPhoneNumber = editUserAccountViewModel.AltPhoneNumber;
user.EmailAddress = editUserAccountViewModel.EmailAddress;
user.LanguageId = context.languages.Where(t => t.Code == editUserAccountViewModel.Language).Select(t => t.Id).FirstOrDefault();
user.CreatedDate = DateTime.Now;
user.UserId = WebSecurity.GetUserId(editUserAccountViewModel.UserName);
context.Entry(user).State = EntityState.Modified;
context.SaveChanges();
JsonResult res = Json(new { Success = true, data = "", Message = "" });
return res;
}
JsonResult res2 = Json(new { Success = false, data = "", Message = "" });
return res2;
}
My ModelState.IsValid returns false and I added the errors statement above and it says:
It's because UserName, Password and Confirm Password fields are required. How can I reuse my viewmodel so that I can those fields required when I'm creating an account but not required on editing an account? That is, I want to keep those fields are the same (not editable in edit account?)
You could output some dummy values into hidden fields in the view so they won't be null when they come back in, however, that is more data being pushed back and forth.
Secondly, you can remove the keys from the modelstate and thus remove their errors.
ModelState.Remove("UserName");
ModelState.Remove("Password");
ModelState.Remove("ConfirmPassword");
After doing that, when you call ModelState.IsValid, it will give the answer based only on the fields you are interested in
HTH
MVC5 is storing my passwords in plaintext. I don't want to use the default hashing algorithm, as I'm required to use e.Encrypt() instead. I'm creating a registration function, and I need to know how I can edit values from FormCollection before using TryUpdateModel.
Here's the code:
[HttpPost]
public ActionResult Register([Bind(Include = "User,Pass,Email")] FormCollection form)
{
var user = new Users();
string Hash = e.Encrypt(form["Pass"]); // Gets set.
if (TryUpdateModel(user, form))
{
context.Entry(user).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Login", "Account");
}
return View();
}
I've searched high and low, and everything I've found is irrelevant to my needs.
I've tried this:
form["Password"] = e.Encrypt(form["Password"])
...and that compiles, but when debugging, the value never gets set. e.Encrypt() does work as a function, so it's not that.
What am I doing wrong?
I figured it out after some trial and error:
[HttpPost]
// Remove FormCollection and replace with UserModel.
public ActionResult Register([Bind(Include= "Username,Password,EmailAddress")] UserModel user)
{
if (TryUpdateModel(user))
{
// Set password in TryUpdate success.
user.Password = e.Encrypt(user.Password);; // Set correctly
context.Entry(user).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Login", "Account");
}
return View();
}
However, another issue popped up, DataValidationError. The issue is from the UserModel.cs class:
[RegularExpression(RegexPassword, ErrorMessage = ErrorPassword)]
I had a regular expression which didn't match the hash, so when I tried to update, it was unable to validate. That's a problem for another thread, but I just removed it for now.
Ive got a problem: my websecurity always throws false on confirmation. What am I doing wrong?
Here is my Validate Action(Ive debugged it, the id received is the right confirmation token:
public ActionResult Validate(String Id)
{
if (String.IsNullOrEmpty(Id))
{
return View();
}
bool b = WebSecurity.ConfirmAccount(Id);
if (b)
{
return View("ConfirmationSuccess");
}
return View("ConfirmationFailure");
}
And here is my registration action:
public ActionResult Register(RegisterModel model, string ReturnUrl)
{
if (ModelState.IsValid)
{
// Попытка зарегистрировать пользователя
try
{
string confirmationToken = WebSecurity.CreateUserAndAccount(model.rEmail.ToLower(), model.rPassword, null, true);
dynamic email = new Email("~/Views/Emails/RegisterConfirmation.cshtml");
email.To = model.rEmail;
email.ConfirmationToken = confirmationToken;
email.Send();
return RedirectToAction("EmailValidation", new { Email = model.rEmail.ToLower() });
}
catch (MembershipCreateUserException e)
{
string field = string.Empty;
switch (e.StatusCode)
{
case MembershipCreateStatus.DuplicateUserName:
field = "rEmail";
break;
case MembershipCreateStatus.InvalidPassword:
field = "rPassword";
break;
default:
field = "RegisterForm";
break;
}
ModelState.AddModelError(field, ErrorCodeToString(e.StatusCode));
}
}
ViewBag.RegisterModel = model;
ViewBag.ReturnUrl = ReturnUrl;
ViewBag.LoginModel = new LoginModel();
//ModelState.AddModelError("rEmail", "Пользователь с таким e-mail уже зарегистрирован");
// Появление этого сообщения означает наличие ошибки; повторное отображение формы
return View("Login");
}
after registration the email is sent, the link is the right link, but when It goes to WebSecurity.ConfirmAccount(Id), it always throws false....
Thank you for your time and sorry for my bad English.
UPD:
There is a converter for all urls to lower on my IIS server. Could it be the case, that it compares keys case-sensitive? And how can I fix this?
UPD:
Ok, the problem is really in lowercase url. WebSecurity.ConfirmAccount is case-sensitive... Ive made a little workaround in my action so that I could get the right ConfitmtionToken, but this is not fully right way, while there could be two identical ConfirmationToken.ToLower() as I think, So, please, somebody, point me the right way to do it.
And here is my workaround:
public ActionResult Validate(String Id)
{
if (String.IsNullOrEmpty(Id))
{
return View();
}
//bool b = WebSecurity.ConfirmAccount(Id);
using (var ctx = new DBContext())
{
Id = ctx.wpMembership.Where(s => s.ConfirmationToken.Equals(Id, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault().ConfirmationToken;
}
if (WebSecurity.ConfirmAccount(Id))
{
return View("ConfirmationSuccess");
}
return View("ConfirmationFailure");
}
This is because you are passing the account Id not the confirmation token. You need to pass the confirmation token.
The confirmation token is generated by the CreateAccount(String, String, Boolean), CreateUserAndAccount(String, String, Object, Boolean), and GeneratePasswordResetToken(String, Int32) methods. The token is ordinarily sent to the user as a link in an email message, and the user clicks the link in order to verify his or her identity.
I'm creating a client contact using Membership.CreateUser and I'd like to supply a generated password each time a contact is created. The production team will be setting up accounts for new client contacts.
The problem I'm running into is when I pass through the generic password, the method fails. When I supply my own password through the model it works fine.
I'm looking for a work-around or a better practice.
Thanks in advance for you help!
Here is an example of the code:
[HttpPost]
public ActionResult RegisterClientContact(RegisterClientContactViewModel model)
{
if (ModelState.IsValid)
{
//Create Generic password - First initial Last initial 1234
string genericPassword = model.FirstName.Substring(0, 1).ToLower() + model.LastName.Substring(0, 1).ToLower() + "1234";
//Attempt to register the Client Contact
MembershipCreateStatus createStatus;
Membership.CreateUser(model.Email, genericPassword, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
Roles.AddUserToRole(model.Email, "Client");
FormsAuthentication.SetAuthCookie(model.Email, false /*create persistent cookie*/);
Person person = new Person();
person.FirstName = model.FirstName;
person.LastName = model.LastName;
person.Email = model.Email;
person.Phone = model.Phone;
person.Active = true;
db.Persons.Add(person);
db.SaveChanges();
ClientContact clientPerson = new ClientContact();
clientPerson.ClientPersonId = person.Id;
clientPerson.Title = model.Title;
clientPerson.ClientId = model.ClientId;
db.ClientPersons.Add(clientPerson);
db.SaveChanges();
return RedirectToAction("Index", "ClientContact");
}
}
return View("An Error has occured");
}
By the way, it seems I should be able to pass in my own password given this article.
It's likely due to the fact that your default passwords don't meet the ASP.NET Membership default password complexity requirements. The default password needs to be 7 characters. The exception should tell you exactly what the problem is. See the following MSDN page for more details on how to configure Membership's minRequiredPasswordLength setting.
Membership.MinRequiredPasswordLength Property