Search code examples
c#entity-frameworkasp.net-coreasp.net-identity

What requirements are needed to satisfy UserManager dependencies in ASP.NET Core?


Due to a number of factors on the project I am required to create an instance of UserManager manually, without the use of DI. I am doing so as follows:

        var userstore = new UserStore<ApplicationUser>(_userContext);

        IPasswordHasher<ApplicationUser> hasher = new PasswordHasher<ApplicationUser>();

        var validator = new UserValidator<ApplicationUser>();
        var validators = new List<UserValidator<ApplicationUser>> { validator };

        var result = new UserManager<ApplicationUser>(userstore, null, hasher, validators, null, null, null, null, _userLogger);

This is done in a helper class, and the result is returned to the calling class, my controller.

In my controller I make the following call:

var user = await _userManager.GetUserAsync(User);

...however, user is always null. I am assuming that some of the nulls I'm passing in to the UserManager constructor need to be filled in, but I'm only guessing. I actually got the basis for this code from another SO questions that I'm having trouble locating right now.

I don't think there's any magic in what I'm doing to configure some of the other dependencies. I use a DbContext called ApplicationDbContext that looks like so:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{


    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {   }

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

I wire it up in Startup.cs as such:

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));

And I have an AppliactionUser that comes in from a shared project.

When I do a:

var users = userManager.Users.ToList();

I get a list of all the expected users.

Why does the call to GetUserAsync return null?

Edit & Update

I just wanted to clarify that I am indeed using DI throughout the project, I just can't use it for UserManager. The reason is that the dependencies are typically wired up for it through the .AddIdentity() call, which we're not using on this project. Adding the default identity services trumps the OIDC configuration we're using, adds middleware we aren't using and prevents the user from properly authenticating.

The UserManager is indeed the normal ASP.NET Core one. But the difference is that we're using OIDC to log the user in.


Solution

  • The ApplicationUser was being returned as null because the expected claims were not present on the authenticated user. The UserManager is wired up correctly, save a config change that allows the user to be resolved by a differently named claim.

    We are using IdentityServer 4 as the backing identity provider on the project. The claim type that identifies the user is called sub, which is not normally inspected by the UserManager.

    In the end, I created a quick class (which I'll likely be able to refactor out to something inline) that looks like so:

    public class OptionsProvider : IOptions<IdentityOptions>
    {
        public IdentityOptions Value { get
            {
                var result = new IdentityOptions();
                result.ClaimsIdentity = new ClaimsIdentityOptions { UserIdClaimType = "sub" };
                return result;
            }
        }
    }
    

    Next I updated the instantiation of the UserManager to include the options:

    var result = new UserManager<ApplicationUser>(
        userstore, 
        new OptionsProvider(), 
        hasher, 
        validators, 
        null, null, null, null, 
        _userLogger);
    

    This did the trick. Thanks again to @trailmax for pointing me at the source, which I should admittedly do a little more often.