Search code examples
asp.net-coreasp.net-identity-3

Method FindByLoginAsync doesn't work correctly with AspNetCore.Identity


I have used the AspNetCore.Identity in my Asp.Net Core application and I want to call the method called FindByLoginAsync but result is always NULL.

Versions:

Microsoft.AspNetCore.Identity.EntityFrameworkCore (1.1.1)
Microsoft.AspNetCore.Identity (1.1.1)

Code:

var loginProvider = "Github"
var providerKey = "1234567";
var user = await _userManager.FindByLoginAsync(loginProvider, providerKey);

This record exists in the database, but this method returns always NULL.

I've tried trace the SQL query and I've got this:

exec sp_executesql N'SELECT TOP(1) [e].[ProviderKey], [e].[LoginProvider], [e].[ProviderDisplayName], [e].[UserId]
FROM [UserLogins] AS [e]
WHERE ([e].[ProviderKey] = @__get_Item_0) AND ([e].[LoginProvider] = @__get_Item_1)',N'@__get_Item_0 nvarchar(450),@__get_Item_1 nvarchar(450)',@__get_Item_0=N'Github',@__get_Item_1=N'1234567'

My SQL query is like [e].[LoginProvider] the value providerKey and [e].[ProviderKey] the value loginProvider.

Application DbContext

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<ApplicationUser>(i =>
            {
                i.ToTable("Users");
                i.HasKey(x => x.Id);
            });
            builder.Entity<ApplicationRole>(i =>
            {
                i.ToTable("Roles");
                i.HasKey(x => x.Id);
            });
            builder.Entity<IdentityUserRole<int>>(i =>
            {
                i.ToTable("UserRoles");
                i.HasKey(x => new { x.RoleId, x.UserId });
            });
            builder.Entity<IdentityUserLogin<int>>(i =>
            {
                i.ToTable("UserLogins");
                i.HasKey(x => new { x.ProviderKey, x.LoginProvider });
            });
            builder.Entity<IdentityRoleClaim<int>>(i =>
            {
                i.ToTable("RoleClaims");
                i.HasKey(x => x.Id);
            });
            builder.Entity<IdentityUserClaim<int>>(i =>
            {
                i.ToTable("UserClaims");
                i.HasKey(x => x.Id);
            });
            builder.Entity<IdentityUserToken<int>>(i =>
            {
                i.ToTable("UserTokens");
                i.HasKey(x => x.UserId);
            });
        }
    }

Implementation of IdentityUser, IdentityRole

public class ApplicationUser : IdentityUser<int>
{

}

public class ApplicationRole : IdentityRole<int>
{
}

How can I fix this? How is this behaviour possible?


Solution

  • You have incorrect order of primary keys in registration of entity IdentityUserLogin. Change it to this

    builder.Entity<IdentityUserLogin<int>>(i =>
    {
        i.ToTable("UserLogins");
        i.HasKey(x => new { x.LoginProvider, x.ProviderKey });
    });
    

    That's the fix, now the rationale behind.

    In version 1.1.1 the method UserStore.FindByLoginAsync used method DbSet.FindAsync, which accepts ordered array of values for primary keys. The order must follow the order used in entity registration.

    public async virtual Task<TUser> FindByLoginAsync(string loginProvider, string providerKey,
            CancellationToken cancellationToken = default(CancellationToken))
    {
       ...
       var userLogin = await UserLogins.FindAsync(new object[] { loginProvider, providerKey }, cancellationToken);
       ...
    }
    

    In the default implementation the primary keys are registered in correct order

    builder.Entity<TUserLogin>(b =>
    {
        b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
        b.ToTable("AspNetUserLogins");
    });