2

I'm using ASP.NET Core Identity (2.0) with all the default code. I changed the AccountController.Login method to check a legacy database and migrate users when a user is not found. Since that old database had both usernames and emails, I'm populating both fields in the newly created user.

But then, if I try to log in again with a migrated user, using the email doesn't work, as it seems the SQL request always look for WHERE u.NormalizedUserName = @__normalizedUserName_0 only.

How can I enable a single user with an email and a username that differ to log in with either their username or their email?

4
  • A technique is described in Allow Login with either Email or Username. Commented Jun 12, 2018 at 23:42
  • This technique addresses the changes required to the view for validation, but this does not address the problem above. A user created with a username can use a username to log in. A user created with an email can use an email to log in, but a user with both (different values) won't be able to log in with an email. Commented Jun 13, 2018 at 1:28
  • I'm not sure what you stated is correct. The code can login with either username or email even if they are different. The former will go straight through to PasswordSignInAsync() but if email is used (detected by @ symbol) then an intermediate lookup for username using FindByEmailAsync() is used. Commented Jun 13, 2018 at 4:23
  • Then the templates we're using are outdated. The only calls to FindByEmailAsync I see are in ForgotPassword and ResetPassword. Is this in the scaffolded 2.1 controller? Ours were generated with 2.0. Commented Jun 13, 2018 at 12:09

3 Answers 3

8

You have the possibility to use CheckPasswordAsync(user, password) method from UserManager: https://github.com/aspnet/Identity/blob/a273e349eea2be3a40b16e6947b2deab95f4b5b2/src/Core/UserManager.cs#L692

Right before calling that method you can fetch your user in the following manner:

 var user = await _userManager.FindByNameAsync(userName) ?? await _userManager.FindByEmailAsync(userName);

Based on your current UserStore implementation you may need to adjust FindByNameAsync or FindByEmailAsync or both.

Sign up to request clarification or add additional context in comments.

Comments

6

Thank you to all contributors, but I think this question deserves a more precise answer.

The default templates of ASP.NET Core Identity will always create users with both the email and username containing the same value. Then, the default login handler (either in AccountController.Login or the OnPostAsync method of the Login view) will always consume the "email" field as being the username, and call PasswordSignInAsync. Though the code seems to imply it's supporting emails, this method really only accepts a username. To add to the whole confusion, the view is making validation that you entered a valid email, but what you enter will eventually be searched in the NormalizedUserName field!

Thus, if you want to allow emails as well, as stated by Mark and Razvan, you can use _userManager.FindByEmailAsync to get the actual username of the user trying to log in, then call PasswordSignInAsync with the username.

As stated by Barry Dorrans: "No you're right. The templates end up with username and email being the same during registration, but fail to keep them in sync. There's work scheduled for 2.2 to fix this ugly mess.".

An open issue here.

Comments

0
  1. You must change AccountViewModel.cs

    public class LoginViewModel
        {
            [Required]
            [Display(Name = "Логин/Email")]
            //[EmailAddress]
            public string Email { get; set; }
    
            [Required]
            [DataType(DataType.Password)]
            [Display(Name = "Пароль")]
            public string Password { get; set; }
    
            [Display(Name = "Запомнить меня")]
            public bool RememberMe { get; set; }
        }
    
  2. AccountController.cs

    
    public async Task Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

    ApplicationUser user; if (model.Email.Contains("@")) user = UserManager.FindByEmail(model.Email); else user = UserManager.FindByName(model.Email); var result = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, shouldLockout: false); switch (result) { case SignInStatus.Success: return RedirectToLocal(returnUrl); case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); case SignInStatus.Failure: default: ModelState.AddModelError("", "Неудачная попытка входа."); return View(model); } }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.