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.
Related
Complete error message:
InvalidOperationException: The instance of entity type 'ApplicationUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
I get this error when I try to update user info on View page.
Updated code:
[HttpGet]
public ActionResult Edit(string id)
{
//Get user and return the Edit View
ApplicationViewModel model = db.Users.Where(u => u.Id == id)
.Select(u => new ApplicationViewModel()
{
UserName = u.UserName,
ClearTextPassword = u.ClearTextPassword,
PhoneNumber = u.PhoneNumber,
Enabled = u.Enabled
// Add the remainder properties
}).FirstOrDefault();
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, ApplicationViewModel model)
{
if (ModelState.IsValid)
return View(model);
var user = await _userManager.FindByIdAsync(id);
if (user == null)
return NotFound();
user.UserName = model.UserName;
user.ClearTextPassword = model.ClearTextPassword;
user.Enabled = model.Enabled;
user.PhoneNumber = model.PhoneNumber;
{
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
//db.Entry(listdata).State = EntityState.Modified;
//db.SaveChanges();
return RedirectToAction("Index");
}
}
return View(user);
}
I expect the user info to save to the database and show the change on the Home page after clicking Save button.
And, this is why you should use view models. There's actually additional reasons besides just this one specific exception.
First, the explanation of what's happening. Somewhere in your codebase, during the processing of this request an instance of ApplicationUser with the same id as what you're attempting to edit was queried. This could have been caused by any number of different things: the important part is that your context is already tracking this particular instance.
When you bind the post directly to ApplicationUser here in your action, you're creating an entirely different instance. Adding that new instance directly to your context, attempts to start tracking that instance as well and fails because there's conflict with what your context is already tracking.
Two takeaways:
When editing an entity ALWAYS pull it fresh from the database, alter it as necessary, and then save that instance back to the database.
You should NEVER directly save anything created from the post (i.e. the user param of your action here) to your database. There's a whole host of reasons why you should not do this, but security is first and foremost. Post data is data from the user and NEVER trust the user.
Using a view model fixes both of these issue. You simply create a class like ApplicationUserViewModel (the name doesn't matter) and then add properties to that only for the data you want to allow the user to modify. In other words, you'd exclude things like an ID (IDs should ALWAYS come from the URL), created date and other audit trail stuff, etc. Then, you bind the post to this view model, pull the actual entity you want to modify from the database, map the relevant data onto that entity and then save that entity.
Added altogether, that would make your action look something like:
[HttpPost("{id}"]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, ApplicationUserViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var user = await _userManager.FindByIdAsync(id);
if (user == null)
return NotFound();
user.FirstName = model.FirstName;
user.LastName = model.LastName;
// etc. You can also use a mapping library like AutoMapper instead
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
// db.Entry(listdata).State = EntityState.Modified;
// db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
I have the following register method, and I swear I (manually) tested this a while back and noted that if a username already existed, the result simply had a false value for result.Succeeded and would append the error message to the ModelState (using the build in AddErrors(result) helper method). I'm pretty sure this method (Register(...)) comes out of the box with ASP.NET mvc 5, but I think I changed the user to include a username (whereas out of the box, the email is simply used as the username).
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Username, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Instead, I am currently getting the error as an EntityValidationError being thrown and uncaught.
I know I could simply catch this error and move on with my day, but I want to make sure that something else is causing this issue if it is not the correct behavior.
Update:
After creating a new MVC project, I can confirm that the typical behavior (when a duplicate username is registered) is that CreateAsync should return a result with a false value for result.Succeeded and an error message of "Username is taken" should be appended to the ModelState. Clearly something is amiss in my code or config, but I haven't the foggiest idea of where to start exploring. If it helps, I have been seeing EntityValidationErrors in other places of my code lately in situations that shouldn't warrant it either. See: Unable to SaveChanges on a db update. Weird lazy loading behavior possibly?
I found my own solution. As I mentioned, I had altered the user to include a username (as well as make email optional). A part of this task involved creating a custom user validator class. In the custom user validator's ValidateAsync method, I had forgotten to check if a username existed already (and did not belong to the user). Like so:
async Task<IdentityResult> IIdentityValidator<TUser>.ValidateAsync(TUser item)
{
var errors = new List<string>();
// ...
// Piece of code I have now added
var owner = await _manager.FindByNameAsync(item.UserName);
if (owner != null && !EqualityComparer<string>.Default.Equals(owner.Id, item.Id))
{
errors.Add($"Username {item.UserName} is already taken");
}
// End of code I added
// ...
return errors.Any()
? IdentityResult.Failed(errors.ToArray())
: IdentityResult.Success;
}
I believe the lesson learned for me is the difference between the App layer validation, where validation occurs in the CreateAsync method by the UserManager. In the case of the App layer validation, the error will present itself exactly as prescribed. If that layer of validation is omitted, and the DB faces the same constraint, then when the context is saved, it will throw its own error. In this case, a slightly more cryptic EntityValidationError.
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 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);
I'm really just starting to get into this LinkedIn API as well as ASP.NET MVC so bear with me. I'm trying to authenticate my user, which appears to be working, but when I try to store the accessToken value (which also appears to be valid) I'm getting the error:
"Exception Details: System.InvalidOperationException: UserId not found."
The error occurs at the comment "// ERROR OCCURS HERE"
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
var userIdClaim = claimsIdentity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
// userIdClaim.Value here is: 5-0Vfh4Gv_
var accessToken = claimsIdentity.FindAll(loginInfo.Login.LoginProvider + "_AccessToken").First();
if (accessToken != null)
{
// gets to this point, but...
await UserManager.AddClaimAsync(userIdClaim.Value, accessToken); // ERROR OCCURS HERE
}
}
}
... ... ...
}
This code is 99% from the MVC application template in Visual Studio. The only things I've changed are to add the linkedin NuGet package (install-package linkedin) and set up my API key/secret in Startup.Auth.cs. It's entirely possible (likely, even) that I'm just doing some things out of order or getting the user ID incorrectly.
I've looked at every example and video I can find and still can't figure this out.
Can anyone help me with this user error message, and also, am I just missing some general best practices kind of things? Feeling lost and frustrated...
Thank you!
The error message says it all. I don't think you are getting the user Id correctly.
Try
await UserManager.AddClaimAsync(claimsIdentity.GetUserId(), accessToken);
instead of
await UserManager.AddClaimAsync(userIdClaim.Value, accessToken);
The issue is that at this point:
await UserManager.AddClaimAsync(userIdClaim.Value, accessToken);
The first parameter is expected to be the UserId of the user in the identity database table (i.e. aspnetusers).
You are passing in the nameidentifer from LinkedIn - and thus, that user does not exist in your database.
I think you need to check first whether or not the external user is already registered, and if not, then create the account, add the external login and then add the claim.