UserManager.FindByEmailAsync returns null, but the user exists in the database
Asked Answered
R

3

11

UserManager.FindByEmailAsync returns null, but the user exists in the database.

The code below explain the strange problem:

var email = info.Principal.FindFirstValue(ClaimTypes.Email);
var test = new Data.ApplicationDbContext().Users.First(x => x.NormalizedEmail == email);
var usermail = await _userManager.FindByEmailAsync(email);

Console.WriteLine(test == null);      //false
Console.WriteLine(usermail == null);  //true

EDIT

Also via _userManager itself, the desired user is obtained:

var test = _userManager.Users.FirstOrDefault(x => x.NormalizedEmail == email);
var usermail = await _userManager.FindByEmailAsync(email);

Console.WriteLine(test == null);      //false
Console.WriteLine(usermail == null);  //true

It should be noted that the user was not created in a "conventional" manner, but by Data-Seed (in OnModelCreating):

protected override void OnModelCreating(ModelBuilder builder)
{
    var users = new (string email, string name)[] {
        ("[email protected]", "admin")
    };

    var appUsers = users.Select(x => new ApplicationUser
    {
        Email = x.email,
        NormalizedEmail = x.email,
        NormalizedUserName = x.email,
        UserName = x.email,
        EmailConfirmed = true,
        Id = Guid.NewGuid().ToString(),
        SecurityStamp = Guid.NewGuid().ToString()
    }).ToArray();

    var role = new IdentityRole("Admins") { Id = Guid.NewGuid().ToString() };
    var roleToUser = appUsers.Select(x => new IdentityUserRole<string> { RoleId = role.Id, UserId = x.Id });

    builder.Entity<ApplicationUser>().HasData(appUsers);
    builder.Entity<IdentityRole>().HasData(role);
    builder.Entity<IdentityUserRole<string>>().HasData(roleToUser);
        
    base.OnModelCreating(builder);
}
Regulate answered 14/1, 2019 at 7:20 Comment(10)
You did not say what are your actually wanting? You just presented some code and then said nothing!Cybil
@Cybil Can you point to missing information after the title and code? I expect that (await _userManager.FindByEmailAsync(email)) == new Data.ApplicationDbContext().Users.First(x => x.NormalizedEmail == email);Regulate
var email = info.Principal.FindFirstValue(ClaimTypes.Email); is returning the email that is being used for external login. So have you created an user with that email please?Cybil
Can you add your ApplicationDbContext() code please?Cybil
I checked the UserManager's FindByEmailAsync which calls FirstOrDefaultAsync on the Users set just like you do. The only difference between your query and what the UserStore does would in line 1414 but this can be ignored since FindByEmail returns null for you.Lunular
Therefore: Is the Users set in the DbContext really associated with the User class registered with the IdentityUser? I mean maybe the _userManager is operating on a completely different db set or it operates on an InMemoryUserStore?Lunular
For better debugging, could you compare dbContext.Users.Count() with _userManager.Users.Count() to see if they deviate? Or execute your query on the set managed by the UserManager: _userManager.Users.First(x => x.NormalizedEmail == email); and check whether this yields a result. If not, then your dbset is different from what the UserManager operates on...Lunular
@B12Toaster Count()? Much more than that! _userManager.Users.FirstOrDefault(x => x.NormalizedEmail == email) return the desired user... i edit the question, thank!Regulate
How did you create User by Data-Seed, share us the code.Kathykathye
@TaoZhou i added the code.Regulate
L
11

As you can see in the source-code links in the comments I made to your OP FindByEmailAsync performs a NormalizeKey before it actually starts searching for the user.

email = NormalizeKey(email);

This NormalizeKey(email) is done by the UpperInvariantLookupNormalizer that will do the following string operation on your email

return key.Normalize().ToUpperInvariant();

Now the part of your code that is causing the "strange" behaviour is the missing call to normalize in your code when creating the user:

var appUsers = users.Select(x => new ApplicationUser
{
    Email = x.email,
    NormalizedEmail = x.email,
    NormalizedUserName = x.email,
    UserName = x.email,
    EmailConfirmed = true,
    Id = Guid.NewGuid().ToString(),
    SecurityStamp = Guid.NewGuid().ToString()
}).ToArray();

Not normalizing the email will still make it discoverable via the users table as this simply compares the NormalizedEmail (which you did not normalize when you created the user) with the not-normalized email you pass as argument:

_userManager.Users.FirstOrDefault(x => x.NormalizedEmail == email);

...however, userManager.FindByEmailAsync will normalize it first and afterwards do the search...

_userManager.FindByEmailAsync(email);

...and therefore not find the user.

Change your code to the following:

// inject via using Microsoft.AspNetCore.Identity
protected ILookupNormalizer normalizer;

var appUsers = users.Select(x => new ApplicationUser
{
    Email = x.email,
    NormalizedEmail = normalizer.Normalize(x.email),
    NormalizedUserName = normalizer.Normalize(x.email),
    UserName = x.email,
    EmailConfirmed = true,
    Id = Guid.NewGuid().ToString(),
    SecurityStamp = Guid.NewGuid().ToString()
}).ToArray();
Lunular answered 15/1, 2019 at 10:11 Comment(0)
S
2

I solved this problem just by clearing the users that were registered and I registered again, soon after FindByEmailAsync() started to find the Users, I'm not sure why this happens but I'll leave my report here because it might help someone. good luck!

Scharaga answered 22/4, 2023 at 22:40 Comment(1)
Interesting, I had the same problem. Not sure why it then starts to work with the exact same email.Suchta
K
0

For NormalizedEmail and NormalizedUserName, it should be uppercase letter.

Try

var appUsers = users.Select(x => new ApplicationUser
{
    Email = x.email,
    NormalizedEmail = x.email.ToUpper(),
    NormalizedUserName = x.email.ToUpper(),
    UserName = x.email,
    EmailConfirmed = true,
    Id = Guid.NewGuid().ToString(),
    SecurityStamp = Guid.NewGuid().ToString()
}).ToArray();
Kathykathye answered 15/1, 2019 at 6:28 Comment(1)
I would not use this in production as it deviates from the way Identity normalizes values. Simply using ToUpper is problematic as it depends on the current culture which may not contain uppercase variants; Also a call to normalize is missing. So FindByEmailAsync still may not necessarily work with international domains.Lunular

© 2022 - 2024 — McMap. All rights reserved.