I am using MVC 5's scaffold-ed code that generates a login method. I followed the official tutorial from...
Create a secure ASP.NET MVC 5 web app with log in, email confirmation and password reset (C#)
...to add the additional functionality of making sure email is confirmed before user can log in to the system.
The following is my code in the controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var currentUser = UserManager.FindByNameAsync(model.Email);
if (currentUser != null)
{
if (!await UserManager.IsEmailConfirmedAsync(currentUser.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
// Other scaffolded implementations
}
However, Visual Studio comes up with an error that states that the argument is invalid for the method IsEmailConfirmedAsync. Apparently, I checked and the currentUser.Id is an int datatype and is the id for the System.Threading.Task. How do I fix this so that what I pass is the UserId instead of the Task Id?
That is because in your code currentUser is being assigned the Task returned from finding the user.
You should await that call to get the desired behavior
var currentUser = await UserManager.FindByNameAsync(model.Email);
Even the example linked to the OP has it that way
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
Related
I want to be able to log into my website with an email, but it appears as if Identity is set up so that the signInManager accepts usernames not emails to log in. I've seen posts where they use FindByEmail() to get the username and then pass that in, but when I try to use it I get a red squiggly saying that
"UserManager< IdentityUser> does not contain a definition for 'FindByEmail' and no accessible extension method 'FindByEmail' accepting a first argument of type 'UserManager< IdentityUser>'"
I think it is because I am using Asp.Net Core not Asp.Net.
public async Task<IActionResult> Login(LoginModel model)
{
if (ModelState.IsValid)
{
var signedUser = userManager.FindByEmail(model.Email);
var result = await signInManager.PasswordSignInAsync(signedUser, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
return RedirectToPage("/Index");
}
ModelState.AddModelError(string.Empty, "Invalid Email or Password");
}
return View("Pages/Account/Login.cshtml", model);
}
Based on the documentation, there is no method FindByEmail. Only FindByEmailAsync exists. so, change your code to as below
var signedUser = await userManager.FindByEmailAsync(model.Email);
The default project template in Visual Studio 2017 contains a function in the ManageController for the logged in User to change their password.
Following a successfull password change the user is then automatically signed in again
await _signInManager.SignInAsync(user, isPersistent: false);
What is the purpose of this sign in?
The full action method is below:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (!changePasswordResult.Succeeded)
{
AddErrors(changePasswordResult);
return View(model);
}
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToAction(nameof(ChangePassword));
}
Inside of ChangePasswordAsync, there is a call to UpdatePasswordHash, which itself makes a call to UpdateSecurityStampInternal. The implementation of UpdateSecurityStampInternal is not important - what is important is that this (obviously) updates the SecurityStamp property of the user.
Looking into how SignInManager works, you'll see that SignInAsync ends up with a call to UserClaimsPrincipalFactory's CreateAsync method, which itself calls into GenerateClaimsAsync. Inside of this implementation, you'll see the following:
if (UserManager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}
This means that after changing a password, the existing SecurityStampClaimType value will have changed. Reissuing a sign-in action ensures that a new ClaimsIdentity is created, which includes the new value of SecurityStamp.
This may not be the only reason for this sign-in action, but it does appear to be a reason.
I'm using ASP.NET Identity, and I have the basic Forgot Password/Reset Password functionality in place.
When you fill out the form that you forgot your password, it creates a reset token using _userManager.GeneratePasswordResetTokenAsync(user)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
return View("ForgotPasswordConfirmation");
}
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
I noticed that the only validation the Reset Password page has is to check if the code is null, rather than also checking to see if it's still valid or not expired.
[HttpGet]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null)
{
if (code == null)
{
throw new Exception("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
It doesn't actually check to see if the token is valid until you attempt to reset your password and it calls _userManager.ResetPasswordAsync(user, model.Code, model.Password)
I'd like to be able to validate that the code is still valid when they hit the Reset Password page to display a message to the user, and not after they attempt to reset their password.
Is there a way to check if it's valid?
Following code works to verify if the reset token is valid:
1. Create code and send it to user
var code = await this._userManager.GeneratePasswordResetTokenAsync(user);
2. Verify token
[HttpGet]
public async Task<IActionResult> ResetPassword(string UserId, string token)
{
...
ApplicationUser user = //get user;
if(!await this._userManager.VerifyUserTokenAsync(user,this._userManager.Options.Tokens.PasswordResetTokenProvider, "ResetPassword", token)){
ViewBag.Message = this._localizer["tokenNotValid"].Value;
}
...
}
See UserManager code: https://github.com/aspnet/Identity/blob/rel/2.0.0/src/Microsoft.Extensions.Identity.Core/UserManager.cs#L29
If you check the UserManager.ResetPasswordAsync(...) method, tracing throug to the VerifyUserTokenAsync method, which simply does:
// Make sure the token is valid
var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user);
You can just do this yourself as well, knowing that:
You can ask the DI Framework (e.g. via your controller constructor) for the token provider for your situation;
The purpose is just the hardcoded "ResetPassword" string;
The token is the code the user is using;
The user you should be able to get depending on how your view, e-mail, url, and whatever is set up (the default examples don't cover this I think, but you can easily put the user.Id in the "forgot password url" before the token itself, and extract it when you need it).
Then you can just call ValidateAsync yourself and adjust the view accordingly.
Good Morning
In MVC there is a method in the manage controller being used. to generate a token.
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number);
Does anyone know where this generated token is saved. In the basic MVC example they use it to add a phone number and needs to be verified with the token being sms`d to the cellphone number supplied, this code is used to generate that token. But no where is that token being saved, there is no column in the db and is not being passed to the view in a hidden field. But the strange part is when you enter the code and submit it, it will do a comparison in the post method using the following
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), phoneNumber);
// Send an SMS through the SMS provider to verify the phone number
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
I cannot figure out where the GenerateChangePhoneNumberTokenAsync method will find the generated token to compare with the token being passed in with the model. Do anyone of you have an idea of where this could be found.
Kind Regards
The code is not stored in the database. The code generation and verification are handled internally by ASP.NET Identity. There's a corresponding action method in the "ManageController" that handles the phone number and code verification. Here is the code
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Failed to verify phone");
return View(model);
}
Notice the line that does the verification using the UserId, PhoneNumber and Code.
var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
Cheers.
I'm working on a simple Web Application with C# MVC. It's a log in page. When I'm trying to log in to the system, there's a public Login function which is gets the Username & Password.
This is it:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var db = new Entities2();
var user = db.DEV001_Users.FirstOrDefault(u =>
u.Username == model.Username &&
u.Password == model.Password);
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.DEV001_Users_Id.ToString(),
model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
On the other hand, I've got another controller, called HomeController, which gets the User DB data (Name, Email, Surname, etc.):
var db = new Entities2();
var Username = User.Identity.Name;
var users = db.DEV001_Users.Where(u => u.EmailAddress.ToLower() == Username.ToLower());
var UserModel = new DEV001_Users();
if (users != null)
{
var user = users.First();
UserModel.EmailAddress = user.EmailAddress;
UserModel.GivenName = user.GivenName;
UserModel.Surname = user.Surname;
}
return View(UserModel);
Now, the problem is, when I want to get the UserName with User.Identity.Name, it's giving me the ID of that record, instead of the Username. I don't know what else to do.
You're getting the ID instead of the name because you are putting the ID instead of the name in the auth cookie:
FormsAuthentication.SetAuthCookie(user.DEV001_Users_Id.ToString(), model.RememberMe);
Whatever value you set in the cookie is what you are going to get in User.Identity.Name.
Of course you are getting different results in the two functions as you are accessing different fields: in the first, user.DEV001_Users_Id which is presumably an integer, and in the second user.EmailAddress and others.
Here's the thing: you already have the username in model.Username. You are in fact querying with it as part of the where clause you have supplied to FirstOrDefault so we know for a fact that model.Username is the username in this DEV001_Users row.
So, once you have verified that the user exists in the database and performed any other checks you might be doing, such as checking they are not banned, you can modify your code to the following:
FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
I'm not too keen on the implication here that you are storing the passwords in plain text, by the way.