5

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)[] {
        ("xyz@gmail.com", "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);
}
Amal K
  • 4,359
  • 2
  • 22
  • 44
dovid
  • 6,354
  • 3
  • 33
  • 73
  • You did not say what are your actually wanting? You just presented some code and then said nothing! – TanvirArjel Jan 14 '19 at 13:35
  • @TanvirArjel 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);` – dovid Jan 14 '19 at 13:47
  • `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? – TanvirArjel Jan 14 '19 at 13:51
  • Can you add your `ApplicationDbContext()` code please? – TanvirArjel Jan 14 '19 at 13:56
  • 1
    I checked the UserManager's [FindByEmailAsync](https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/UserManager.cs#L1401) which calls [FirstOrDefaultAsync](https://github.com/aspnet/AspNetCore/blob/master/src/Identity/EntityFrameworkCore/src/UserStore.cs#L634) on the `Users` set just like you do. The only difference between your query and what the UserStore does would in line [1414](https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/UserManager.cs#L1414) but this can be ignored since FindByEmail returns `null` for you. – Felix K. Jan 14 '19 at 21:58
  • 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](https://github.com/aspnet/AspNetCore/blob/master/src/Identity/test/InMemory.Test/InMemoryUserStore.cs)? – Felix K. Jan 14 '19 at 21:59
  • 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... – Felix K. Jan 14 '19 at 22:08
  • @B12Toaster `Count()`? Much more than that! `_userManager.Users.FirstOrDefault(x => x.NormalizedEmail == email)` return the desired user... i edit the question, thank! – dovid Jan 14 '19 at 22:45
  • How did you create User by `Data-Seed`, share us the code. – Edward Jan 15 '19 at 02:31
  • @TaoZhou i added the code. – dovid Jan 15 '19 at 06:19

3 Answers3

7

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();
Felix K.
  • 14,171
  • 9
  • 58
  • 72
1

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!

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();
Edward
  • 28,296
  • 11
  • 76
  • 121
  • 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](https://docs.microsoft.com/en-us/dotnet/api/system.string.toupper?view=netcore-2.2#System_String_ToUpper) which may [not contain](https://stackoverflow.com/a/3550226/2477619) uppercase variants; Also a call to [normalize](https://docs.microsoft.com/en-us/dotnet/api/system.string.normalize) is missing. So `FindByEmailAsync` still may not necessarily work with [international domains](https://stackoverflow.com/a/2071250/2477619). – Felix K. Jan 15 '19 at 11:39