I am trying to implement some basic security for logins on a development website. I am using this code (C#) from https://crackstation.net/hashing-security.htm
For some reason it just doesn't work, the password hash generated is stored in my DB exactly as generated, it just doesn't seem to work when I try to validate a password against the hash using ValidatePassword(string password, string correctHash)
The ValidatePassword is supposed to generate the same hash, but it doesn't.
Does anybody have any experience using this code, or have any code that actually works? I have tried several now, including the one on the MSDN and none seem to fit the bill. It is suggested I shouldn't try to write my own code to achieve this.
Here is my code that calls the method, I have validated during debugging that the inputs are correct.
I have copied the code from the linked website into a static helper class which is called by my service methods below.
public ResultModel Register(string emailAddress, string password, string givenName, string familyName)
{
var _db = new EntityConnection();
if (_db.Users.Any(x => x.isDeleted == false && x.EmailAddress == emailAddress))
return new ResultModel() { success = false, message = "Email Address already registered, please attempt to Login" };
password = HashHelper.CreateHash(password);
var user = new User()
{
EmailAddress = emailAddress,
Password = password,
GivenName = givenName,
FamilyName = familyName,
DateAdded = DateTime.Now,
isDeleted = false,
isApproved = false
};
_db.Users.Add(user);
_db.SaveChanges();
var activationToken = new Token()
{
TokenType = "Account Activation",
User = user,
UserID = user.UserID,
DateAdded = DateTime.Now,
TokenCode = Guid.NewGuid().ToString()
};
_db.Tokens.Add(activationToken);
_db.SaveChanges();
return new ResultModel() { success = true, entity = user };
}
public ResultModel Login(string emailAddress, string password)
{
var _db = new EntityConnection();
var user = _db.Users.Where(x => x.isDeleted == false && x.EmailAddress == emailAddress);
if (!user.Any() || user.Count() > 1)
return new ResultModel() { success = false, message = "Credentials supplied do not match an Account, please try again." };
var existingHash = user.First().Password;
var result = HashHelper.ValidatePassword(password, existingHash);
if (result)
return new ResultModel() { success = true, entity = user.First() };
else
return new ResultModel() { success = false, message = "Credentials supplied do not match an Account, please try again." };
}
Related
We've been running for ~4 months, and so far we haven't had any complaints. Today, we have somebody complain as creating an account isn't working for them. So I tried myself, and debugging I have indeed found an issue.
We're getting an error of
{"UserId not found."}
Which happens at this line
var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
I can not understand why when I try to create an account it works perfectly, but using their email / name it always spits out errors... makes no sense to my inexperienced brain?
Here is my full code
public bool CreateUser(string userName, string Email, string Password, string fName, string lName, string country, string ip)
{
try
{
// Default UserStore constructor uses the default connection string named: DefaultConnection
var userStore = new UserStore<IdentityUser>();
var manager = new UserManager<IdentityUser>(userStore);
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 1,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
};
var user = new IdentityUser() { UserName = userName, Email = Email, EmailConfirmed = false };
IdentityResult result = manager.Create(user, Password);
Utilities u = new Utilities();
var usersID = user.Id;
//u.SendMailConfirmation(Email, usersID, fName);
userDetails(usersID, ip, fName, lName, country);
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity);
return true;
}
catch (Exception ex)
{
return false;
}
}
And you are sure an user does not already exist with the same email?
I am trying to create a user for a mixed authentication project. I am using MixedAuthExtension.cs.
I encounter a problem when I reach this line
IdentityResult result = await UserManager.CreateAsync(user,model.Password);
but the system user is created and the AspNetUser.
Thanks in advance for your help
The current code creates both the system user and ASP.NET user but it fails to sync identity
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<ActionResult> CreateSystemUser(RegisterViewModel model, string key)
{
var _context = new RequestToFillDbContext();
#region Initialise
Initialise(_context);
var password = SecurityHelper.GeneratePassword();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
UserManager.UserValidator = new UserValidator<ApplicationUser>(UserManager) { AllowOnlyAlphanumericUserNames = false };
model.Password = password;
model.ConfirmPassword = password;
ModelState.Clear();
#endregion
string serviveNumber = model.ServiceNumber;
if (!_context.SystemUsers.Any(s => s.EmailAddress == model.EmailAddress)
&& !_context.SystemUsers.Any(s => s.UserName == model.UserName))
{
if (ModelState.IsValid)
{
var emp = new RequestToFillApplicationHelper().GetEmployeeByServiceNumber(serviveNumber);
model.Designation = emp.Designation;
//TODO: Query Db for Id
//var identityManager = new IdentityManager();
var user = new ApplicationUser
{
UserName = model.UserName,
Email = model.EmailAddress,
EmailConfirmed = true,
SystemUser = new SystemUser()
{
FirstName = model.FirstName,
LastName = model.LastName,
UserName = model.UserName,
CompanyName = model.CompanyName,
Designation = model.Designation,
EmailAddress = model.EmailAddress,
IsTemporaryPassword = true,
//TempPasswordExpiryDateTime = DateTime.Now.AddHours(24),
SystemUserTypeId = model.SystemUserTypeId,
ServiceNumber = model.ServiceNumber,
IsActive = true,
IsDeleted = false,
IsLocked = false,
CreatedDateTime = DateTime.Now,
IsPasswordReset = false
}
};
//db.SaveChanges();
try
{
IdentityResult result = await UserManager.CreateAsync(user,model.Password);
//Assign user to role
I get a
"Property set method not found."Line 258
which is below
IdentityResult result = await UserManager.CreateAsync(user,model.Password);
I have a controller method called UserSignIn that's used to authenticate a user. The "Candidate" parameter is a model that contains fields including the contact's email address and password.
The model also contains fields "AgencyID" and "ContactID". These are used so that I know which database to connect to (AgencyID) and which contact record to get (ContactID). The user signing in is a contact at an agency.
[HttpPost()]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UserSignIn(Candidate can)
{
bool is_err = false;
string err = string.Empty;
Candidate c_signed_in = new Candidate();
// check data
if (string.IsNullOrEmpty(can.Email))
{
is_err = true;
err += "<li>Missing email address.</li>";
}
if (string.IsNullOrEmpty(can.AccountPassword))
{
is_err = true;
err += "<li>Missing password.</li>";
}
// get candidate
if (ModelState.IsValid && !is_err)
{
c_signed_in = await Repository.GetCandidate(can.AgencyID, 0, can.Email.ToLower(), can.AccountPassword, hostingEnv.WebRootPath);
if (c_signed_in.ContactID == 0)
{
is_err = true;
err += "<li>No account found. Check your credentials.</li>";
}
}
// check model state
if (!ModelState.IsValid || is_err)
{
Candidate c_current = await Repository.GetBlankCandidate(can, false);
c_current.IsModeSignIn = true;
if (is_err)
c_current.ErrsSignIn = "<ul class=\"text-danger\">" + err + "</ul>";
return View("Agency", c_current);
}
// create claims
var claims = new List<Claim>
{
//new Claim(ClaimTypes.Name, c_signed_in.FirstName + gFunc.SPACE + c_signed_in.FamilyName),
new Claim(ClaimTypes.Sid, c_signed_in.ContactID.ToString()),
new Claim(ClaimTypes.Email, c_signed_in.Email)
};
// create identity
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); // cookie or local
// create principal
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme));
// sign-in
await HttpContext.SignInAsync(scheme: CookieAuthenticationDefaults.AuthenticationScheme, principal: principal);
// add to log
gFunc.AddLogEntry("SignIn Candidate: " + c_signed_in.FirstName + gFunc.SPACE + c_signed_in.FamilyName + " - " + c_signed_in.Email);
// fini
return RedirectToAction("Profile", new { agencyID = c_signed_in.AgencyID, contactID = c_signed_in.ContactID });
}
On success, this method redirects to a method called "Profile" that displays the user's profile.
[HttpGet]
[Authorize]
public async Task<ActionResult> Profile(int agencyID, int contactID)
{
Candidate can = await Repository.GetCandidate(agencyID, contactID, string.Empty, string.Empty, hostingEnv.WebRootPath);
if (can.ContactID == 0)
{
int id = agencyID;
return RedirectToAction("Agency", new { agencyID = id });
}
return View("Profile", can);
}
My URL is now "/Home/Profile?agencyID=5809&contactID=19492
However, I can now just change the contactID in the URL and now I'm on another user's profile without being authorized.
How do I avoid this? Obviously I can't include the password as a parameter in the Profile method because it would simply be visible in the URL. What approach should I be taking?
UPDATE - SOLVED
Thanks to all for your comments. Camilo Terevinto's answer solved my problem.
I added the info I needed to the claims in the UserSignIn method and removed the parameters in the Profile method, where I can retrieve the info I need from the active user. Now I can ensure that only the authorized user can reach the "Profile" controller method.
The only thing I had to change was the direct int cast. My compiler didn't like it, so I just change it to use a parse instead:
int agency_id = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
int contact_id = int.Parse(User.FindFirst(ClaimTypes.Sid).Value);
You can add agencyID and contactID to Claims:
new Claim(ClaimTypes.Sid, c_signed_in.ContactID.ToString()),
new Claim(ClaimTypes.Email, c_signed_in.Email),
new Claim(ClaimTypes.NameIdentifier,c_signed_in.agencyID.ToString())
In controller you can obtain it from logged user data:
[HttpGet]
[Authorize]
public async Task<ActionResult> Profile()
{
int agencyID = (int)User.FindFirst(ClaimTypes.NameIdentifier).Value
int contactID = (int) User.FindFirst(ClaimTypes.Sid).Value
Candidate can = await Repository.GetCandidate(agencyID, contactID, string.Empty, string.Empty, hostingEnv.WebRootPath);
if (can.ContactID == 0)
{
int id = agencyID;
return RedirectToAction("Agency", new { agencyID = id });
}
return View("Profile", can);
}
In my C# application I use 2 connection strings (application_cs, users_cs). To change these connection strings I use:
private static void SetProviderConnectionString(string connectionString)
{
var connectionStringFieldM =
Membership.Provider.GetType().GetField("_sqlConnectionString",
BindingFlags.Instance | BindingFlags.NonPublic);
var connectionStringFieldR = Roles.Provider.GetType().GetField("_sqlConnectionString",
BindingFlags.Instance | BindingFlags.NonPublic);
var connectionStringFieldP = ProfileManager.Provider.GetType().GetField("_sqlConnectionString",
BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringFieldM.SetValue(Membership.Provider, connectionString);
connectionStringFieldR.SetValue(Roles.Provider, connectionString);
connectionStringFieldP.SetValue(ProfileManager.Provider, connectionString);
}
public static void SetProviderUsers()
{
SetProviderConnectionString(ConfigurationManager.ConnectionStrings["users_cs"].ConnectionString);
}
public static void SetProviderApp()
{
SetProviderConnectionString(ConfigurationManager.ConnectionStrings["application_cs"].ConnectionString);
}
So in my code whenever I want to add a user I do this:
public int CreateUser(int stid, int cid, int usrId, string email, string tel, string mob, string username,
bool create, bool prime)
{
int result = 0;
Guid userid = new Guid();
DALUsers.UserDBDataContext dc = new DALUsers.UserDBDataContext();
DAL.AppDataContext d = new DAL.AppDataContext();
BLL.Security.SetProviderUsers();
if (create) //create the user first
{
string question = "1";
string answer = "1";
bool isAproved = true;
string password = System.Web.Security.Membership.GeneratePassword(8, 2);
MembershipCreateStatus cs = new MembershipCreateStatus();
MembershipUser newUser = Membership.CreateUser(username, password, email, question, answer, isAproved, out cs);
Membership.UpdateUser(newUser);
Roles.AddUserToRole(username, "User_x");
if (cs == MembershipCreateStatus.Success)
{
result = 1;
}
else
X.MessageBox.Info("Error", "Cannot create user due to :" + cs.ToString(), UI.Danger).Show();
}
//at this point we have the user created either way.
// return userid;
var id = (from i in dc.aspnet_Users where i.UserName.CompareTo(username) == 0 select i.UserId);
if (id.Count() == 1)
{
userid = id.First();
bool contin = true;
var fulname = (from i in dc.Clients where i.id == usrId select i).First();
if (String.IsNullOrEmpty(fulname.Mobile)) fulname.Mobile = mob;
fulname.Email = email;
fulname.ModifiedBy = HttpContext.Current.User.Identity.Name;
fulname.ModifiedDate = DateTime.Now;
dc.SubmitChanges();
DateTime dt = DateTime.Now;
DALUsers.CIUser usr = new DALUsers.CIUser();
var existing = (from i in dc.CIUsers where i.UserName.CompareTo(username) == 0 && i.cid == cid select i);
if (existing.Count() > 0)
{
X.MessageBox.Info("Warning", "UserName already exists . Please try another!", UI.Warning).Show();
contin = false;
}
else
{
dc.CIUsers.InsertOnSubmit(usr);
dc.SubmitChanges();
}
if (contin)
{
DALUsers.CIUser usrNew = new DALUsers.CIUser();
var approved = (from k in dc.aspnet_Memberships //if user is not approved
where k.UserId == userid
select k).FirstOrDefault();
if (approved.IsApproved == false)
{
approved.IsApproved = true;
}
ProfileBase profile = ProfileBase.Create(username);
profile.SetPropertyValue("Mobile", mob);
profile.SetPropertyValue("Email", email);
profile.Save();
usrNew.UserId = usrId;
usrNew.cid = cid;
usrNew.FullName = fulname.LastName + " " + fulname.FirstName;
usrNew.Role = "User_x";
usrNew.SignRights = prime;
usrNew.IsPrime = prime;
usrNew.stid = stid;
usrNew.UserName = username;
usrNew.UserId = userid;
usrNew.CreatedDate = DateTime.Now;
usrNew.CreatedBy = HttpContext.Current.User.Identity.Name;
dc.CIUsers.InsertOnSubmit(usrNew);
dc.SubmitChanges();
result = 1;
X.MessageBox.Info("Success", "The user has been successfully added", UI.Success).Show();
}
}
else
X.MessageBox.Info("Error", "Could not find the user", UI.Danger).Show();
BLL.Security.SetProviderApp();
return result;
}
EDIT
I just saw that in my code there is this line:
DALUsers.aspnet_User user = new DALUsers.aspnet_User();
But the variable user is not used anywhere else in the code. Probably it has been left there... And its the only variable named user in my code. Is that causing the issue? But then why only on the production server?
EDIT
The weird part is that when I run my application from visual studio locally it works as a charm. But when I am adding a user in the application running on the production server when I am trying to add the second user it fails and I receive this error:
Value cannot be null. Parameter name: user
And if I try to login to my application after that it fails. I have to restart my website from iis to be able to login again.
Any ideas?
Well I cant find the error in your code but if you say that this error occurs only in server and that you are sure that your files are synched between server and your local machine, then probably the error lies in your web.config. Take a look
I'm building a simple application where a user can edit their profile including adding/deleting a brand image. This seems to be working fine and is updating the database no problem, however when refreshing the page and retrieving the user details via Membership.GetUser() the result includes the old results and not those from the updated database.
Here is my MembershipUser GetUser override:
public override MembershipUser GetUser(string query, bool userIsOnline)
{
if (string.IsNullOrEmpty(query))
return null;
var db = (AccountUser)null;
// ...get data from db
if (query.Contains("#")){
db = _repository.GetByQuery(x => x.Email == query).FirstOrDefault();
}
else
{
string firstName = query;
string lastName = null;
if (query.Contains(" "))
{
string[] names = query.Split(null);
firstName = names[0];
lastName = names[1];
}
// ...get data from db
db = _repository.GetByQuery(x => x.FirstName == firstName && x.LastName == lastName).FirstOrDefault();
}
if (db == null)
return null;
ToMembershipUser user = new ToMembershipUser(
"AccountUserMembershipProvider",
db.FirstName + " " + db.LastName,
db.ID,
db.Email,
"",
"",
true,
false,
TimeStamp.ConvertToDateTime(db.CreatedAt),
DateTime.MinValue,
DateTime.MinValue,
DateTime.MinValue,
DateTime.MinValue);
// Fill additional properties
user.ID = db.ID;
user.Email = db.Email;
user.FirstName = db.FirstName;
user.LastName = db.LastName;
user.Password = db.Password;
user.MediaID = db.MediaID;
user.Media = db.Media;
user.Identity = db.Identity;
user.CreatedAt = db.CreatedAt;
user.UpdatedAt = db.UpdatedAt;
return user;
}
Note I am using a custom MembershipProvider and MembershipUser. Here is where I am calling that method:
public ActionResult Edit()
{
ToMembershipUser toUser = Membership.GetUser(User.Identity.Name, true) as ToMembershipUser;
Now when I do a separate query just under this line of code straight to the database, not invoking MembershipUser, I get the updated result which in turn updates the MembershipUser result?!
It seems it may be caching the results? Anyway around this? I hope this is clear enough. Thanks
Edit:
It appears that when I set a breakpoint just after :
// ...get data from db
db = _repository.GetByQuery(x => x.FirstName == firstName && x.LastName == lastName).FirstOrDefault();
'db' retrieves the outdated results though surely this is talking to the database? If need be I'll update with my repository pattern
I managed to find a workaround though I'm not happy with this solution, so if anyone can improve upon this please post.
I decided to manually update the MembershipUser instance manually each time I update the image. My controller now looks like this:
private static ToMembershipUser MembershipUser { get; set; }
// GET: Dashboard/AccountUsers/Edit
public ActionResult Edit()
{
if(MembershipUser == null)
MembershipUser = Membership.GetUser(User.Identity.Name, true) as ToMembershipUser;
}
[HttpPost]
[ValidateJsonAntiForgeryToken]
public JsonResult UploadMedia(IEnumerable<HttpPostedFileBase> files, int id)
{
var images = new MediaController().Upload(files);
if (images == null)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json("File failed to upload.");
}
AccountUser accountUser = db.AccountUsers.Find(id);
db.Entry(accountUser).State = EntityState.Modified;
accountUser.UpdatedAt = TimeStamp.Now();
accountUser.MediaID = images[0];
db.SaveChanges();
MembershipUser.Media = accountUser.Media;
MembershipUser.MediaID = accountUser.MediaID;
return Json(new { result = images[0] });
}
[HttpPost]
[ValidateJsonAntiForgeryToken]
public JsonResult DeleteMedia(int id)
{
bool delete = new MediaController().Delete(id, 1);
if (!delete)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json("Error. Could not delete file.");
}
MembershipUser.Media = null;
MembershipUser.MediaID = null;
return Json("Success");
}