Details of ResetPasswordAsync method of UserManager - asp.net

I want to know how the ResetPasswordAsync() method of .net works from the inside, I tried a lot but unable to find any articles about it, please help.

Below is the code for ResetPasswordAsync()
public virtual async Task<IdentityResult> ResetPasswordAsync(TUser user, string token, string newPassword)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose, token))
{
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
}
var result = await UpdatePasswordHash(user, newPassword, validatePassword: true);
if (!result.Succeeded)
{
return result;
}
return await UpdateUserAsync(user);
}
If user has clicked on forgot password then you need to check if user exists then generate a token key for password reset like below
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");
After that either send recovery mail like shown above or send to your own view for recovery.
Code is taken from Microsoft GitHub Account

Related

User login using Email or Username gives null if user not found in ASP.NET Core MVC

I am using the default identity pages with some modifications, in the login page I included the username for the user to login. It works perfectly, the user now can login by both the email and username, but when the users enters false info, a null exception appears instead of showing
Invalid login attempt
Code:
if (ModelState.IsValid)
{
//Check if user entered email or username in the Input.Email property
var user = await _userManager.FindByNameAsync(Input.Email) ?? await _userManager.FindByEmailAsync(Input.Email);
// user is null if not exist? error
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
//some code
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
I get why the error happens, but don't know what the proper way to solve it.
Originally the signInManager checks the user input not the actual user, so if the input is not found it will not be succeeded, how can I do it the same old way?
when the users enters false info, a null exception appears
In source code of PasswordSignInAsync(TUser, String, Boolean, Boolean) method, we can find it will throw NullException error if user is null.
public virtual async Task<SignInResult> PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
how can I do it the same old way?
You can modify the code as below to check if user is null, and set and display "Invalid login attempt." error message.
var user = await _userManager.FindByNameAsync(Input.Email) ?? await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
//...
//code logic here

How to fix 'Invalid Token' on ASP.NET Identity Email Confirmation

I'm trying to have the confirm email validate a new user account. The token is created and emailed to the user which receive an email with a link to validate the account. When the user clicks on the link, I'm getting INVALID TOKEN.
It is hosted on Godaddy (not sure if it makes any difference)
While debugging the code, I find out that the token being sent to validate is the same generated initially, with the difference that now its lowercase, can this be the problem?
The code to generate the token and email it
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string _code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = userID, code = _code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject, "Please confirm your account by clicking here");
return callbackUrl;
}
To confirm the token/email:
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return RedirectToAction("Create", "Users", new { id = userId });
}
AddErrors(result);
ViewBag.errorMessage = "Error: " + result.Errors;
return View("Error");
}
Also I added machineKey to web.config.
<machineKey validationKey="key" decryptionKey="key" validation="SHA1" decryption="AES" />
All the time I'm getting the error INVALID TOKEN
Encode your code before sending it via email:
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string _code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
_code = HttpUtility.UrlEncode(_code);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = userID, code = _code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject, "Please confirm your account by clicking here");
return callbackUrl;
}
It's unbelievable but the solution was creating a new project and bringing everything to it.
I think something happened while VS created the project that caused the problem.
Thanks for all halp

External login callback signs incorrect user

I have a strange problem with ExternalLoginCallback method. I am logging some information in the log and what is interesting Email is correct, but then userId is incorrect and it belongs to another user who was logged previously.
I.e. some UserA is logged into the system and now UserB wants to log into the system in another window. I am expecting that in new window UserB will be logged in and overwrite cookies, so if I refresh first window it will show UserB, but somehow that is not happening and in second window it shows UserA.
Here is the code:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
EventLogManager.LogWarning(loginInfo.Email);
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: true);
switch (result)
{
case SignInStatus.Success:
{
var userId = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId();
EventLogManager.LogWarning(userId);
...
EDIT
I think I should add more clarifications. There is some action which is being called from third party - Shopify. It looks like:
public ActionResult Callback(string code, string hmac, string shop, string state, string timestamp)
{
//this resolved the issue
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
Session["Workaround"] = 0;
return new ChallengeResult("Shopify", Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = "/Product/Products" }), null, shop.Replace(".myshopify.com", ""));
}

Invalid token in ConfirmEmail due to changed Securitystamp

I've been banging my head against a wall for some time now about this:
I have an ASP.NET MVC 5.2.3 web application with ASP.NET Identity 2.2.1. I want to force users to
validate their email-address and
validate their mobile phone number.
So when a user registers for the application an emailVerification token is generated and sent to the user.
After that the user is redirected to the VerifyPhoneNumber endpoint in the Manage controller. SMS-code is generated and gets send to the user. User is promted to enter the SMS-code. Code is verified.
BUT if then the user receives the email with the email-verification-code and click the link the token cannot no longer be verified (Invalid Token).
As far as I understand, this happens because calling UserManager.ChangePhoneNumberAsync changes the user's SecurityStamp. Email-verification works well if phone verification is not active. To be more specific, when ChangePhoneNumberAsync is not called.
Any ideas on how to prevent the SecurityStamp from changing or allow both verifications on inital registration are greatly appreciated.
Ben
VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var userId = User.Identity.GetUserId();
var result = await UserManager.ChangePhoneNumberAsync(userId, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
else
{
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Could not verify phone number.");
return View(model);
}
ConfirmEmail
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
code = HttpUtility.UrlDecode(code);
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}

Roles Create New DataBase When I Add a user to a role in MVC 5

i create my own database and add user identity table to this by change the connection string.
now my connection string is this:
when i create a new user it worked well.
but when i change the Register(RegisterViewModel model) in RegisterControler to add a user to a role like this code:
public async Task Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//add user to member role******************
if (!Roles.RoleExists("Member"))
Roles.CreateRole("Member");
Roles.AddUserToRole(model.Email, "Member");
//*******************************************
await SignInAsync(user, isPersistent: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
user registerd but dont add to member Role! and it seem there is another connection string for Roles! beacause whene run this code ASPNETDB.MDF created in App_Data!
Please help me to solve this problem
In order to create roles in asp.net identity, you need to use AspNetRoleManager same as you are currently using AspNetUserManager.
The AspNetUserManager may looks like below.
public class AspNetRoleManager : RoleManager<IdentityRole, string>
{
public AspNetRoleManager(IRoleStore<IdentityRole, string> roleStore)
: base(roleStore)
{
}
public static AspNetRoleManager Create(IdentityFactoryOptions<AspNetRoleManager> options, IOwinContext context)
{
return new AspNetRoleManager(new RoleStore<IdentityRole, string, IdentityUserRole>(context.Get<YourDataContext>()));
}
}
Then you need to register AspNetRoleManager in the owin startup. Same like the AspNetUserManager.
app.CreatePerOwinContext<AspNetRoleManager>(AspNetRoleManager.Create);
After that you can use it inside the controller to create roles.
var roleManager = HttpContext.GetOwinContext().Get();
// Check for existing roles
var roleManager = HttpContext.GetOwinContext().Get<AspNetRoleManager>();
var roleExists = await roleManager.RoleExistsAsync("Member");
if (!roleExists)
{
var role = new IdentityRole();
role.Name = "Member";
var result = roleManager.CreateAsync(role);
}
Then add new role to the user.
var user = await UserManager.FindByEmailAsync(model.Email);
var roleRsult = UserManager.AddToRole(user.Id, roleName);

Resources