5

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?

rene
  • 41,474
  • 78
  • 114
  • 152
Kizmar
  • 2,465
  • 3
  • 20
  • 27

1 Answers1

1

[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);
    }

  • When I try to initialize a new UserManager manually, I end up having to pass in a bunch of variables. The only constructor I'm seeing on the UserManager class is this: `public UserManager(IUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger);` – Kizmar Aug 10 '16 at 15:47
  • If we're in Core, shouldn't we be using `new Microsoft.AspNetCore.Identity.UserManager()`? – Kizmar Aug 10 '16 at 16:40
  • Sorry, I was using the wrong version of Asp.Net. I opened a new Asp.Net Core WebApplication project in Visual Studio, and I cannot even find the ApplicationUserService you were taking about. The project's password changing worked. If you need it, here is some documentation: https://docs.asp.net/en/latest/security/authentication/identity.html – Konnor Andrews Aug 10 '16 at 17:38
  • I did notice that I can inject the UserManager and SignInManager into the Controllers directly and it all works as expected. For some reason, when I try to use them in the service layer, that's when I run into issues. – Kizmar Aug 10 '16 at 17:49
  • Is there a specific reason you are directly injecting a new UserManager? – Konnor Andrews Aug 10 '16 at 17:52
  • Not completely sure what you are asking... but Core sets up the DI for the UserManager and SignInManager when you add app.UseIdentity(). I'm injecting these managers into the UserController constructor so I can use them to manage the user data. Previously I was injecting them both into a service layer in an attempt to keep the controller action logic short. – Kizmar Aug 10 '16 at 18:11