Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Core 1 Identity UserManager cached user list not updating

Environment: .Net Core 1, EF, using Identity for authentication, JWT tokens for authorization.

Running into an issue where using the UserManager.ChangePasswordAsync() method IS updating the database properly, but IS NOT updating the UserManager.Users list (assuming).

In Startup.cs, we are simply using app.UseIdentity(), and in the ApplicationUserService constructor, we are injecting the UserManager<ApplicationUser>. We're not doing anything custom outside of that.

For example: Lets say a user changes their password from "password1" to "password2". If that user logs out and back in, the UserManager still thinks the password is "password1". If I restart the WebAPI server, then try to log in; it works as you'd expect with "password2". So it's definitely updating the database, but the scope/cache of UserManager is not being updated.

I'm wondering if the default DI scope of the UserManager is singleton (rather than per request)? I could see that causing this issue if it's not updating the UserStore's cached User list.

Any suggestions? Need more code?

ApplicationUserService (simplified):

private readonly UserManager<ApplicationUser> _userManager;

public ApplicationUserService(UserManager<ApplicationUser> userManager)
{
     _userManager = userManager;
}

public Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
{
    return _userManager.ChangePasswordAsync(user, currentPassword, newPassword);
}

[EDIT]

I'm not sure why this is the case quite yet, but I just realized that if I inject the UserManager and SignInManager into the Controller's constructor directly (instead of into the Service layer), it seems to work just fine.

[EDIT 2]

Summary of findings:

1) Injecting UserManager and SignInManager into a Service constructor, then injecting that Service into a Controller constructor doesn't work completely.

2) Injecting the UserManager and SignInManager into a Controller constructor works.

3) I also tested the use of IServiceProvider in the Controller constructor. I injected IServiceProvider, then set the managers using the GetService method: _userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();. This had the same result as #1.

In #1 & #3: It would save to the database, but the managers seemed to be unaware of the data change when used later on. In both cases, I had to reinitialize the application (stop and start the server) for it to update the cached data.

Shouldn't #3 work the same as #2?

like image 950
Kizmar Avatar asked Nov 09 '22 11:11

Kizmar


1 Answers

[Note: The following code does not apply to Asp.Net Core. For authentication in Asp.Net Core look at this documentation.]

I am new to Asp.Net, but I tried making what you described. For me it works as expected. Maybe my code can spark an idea for you.


    internal static void Main(string[] args)
    {
        _userStore = new UserStore<ApplicationUser>(new IdentityDbContext<ApplicationUser>());
        _userManager = new UserManager<ApplicationUser>(_userStore);

        var x = new ApplicationUser();
        x.UserName = "Test";

        foreach(string error in _userManager.Create(x, "password").Errors)
        {
            Console.WriteLine(error);
        }

        Console.WriteLine(_userManager.CheckPassword(x, "password"));

        var f = ChangePasswordAsync(x, "password", "pass12345");
        f.ContinueWith(delegate
        {
            if (f.IsFaulted)
            {
                Console.WriteLine(f.Exception.Message);
            }
        }).ContinueWith(delegate
        {
            Console.WriteLine(_userManager.CheckPassword(x, "password"));
        });

        Console.ReadKey(true);
    }

    private static UserStore<ApplicationUser> _userStore;

    public class ApplicationUser : IdentityUser { }

    private static UserManager<ApplicationUser> _userManager;

    public static Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
    {
        return _userManager.ChangePasswordAsync(user.Id, currentPassword, newPassword);
    }

like image 171
Konnor Andrews Avatar answered Nov 15 '22 07:11

Konnor Andrews