SimpleTokenProvider icon indicating copy to clipboard operation
SimpleTokenProvider copied to clipboard

How to use UserManager for GetIdentity in Startup.cs class

Open anhlee24 opened this issue 8 years ago • 14 comments

I have inject Usermnanger in startup.cs class like this, but i got the error "Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[TCMS.WebUI.TCMS_Data.Entities.User]' while attempting to activate 'TCMS.WebUI.Startup'.",

Can you suggest something to resolve it? Thank you

 private SignInManager<User> _signInManager;
        private UserManager<User> _userManager;

        public Startup(UserManager<User> userManager, SignInManager<User> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
 private async Task<ClaimsIdentity> GetIdentity(string username, string password)
        {
            var result = await _signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                var user = await _userManager.FindByNameAsync(username);
                var claims = await _userManager.GetClaimsAsync(user);

                return (new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { }));
            }
            return new ClaimsIdentity();
}

anhlee24 avatar Oct 28 '16 13:10 anhlee24

Hey @anhlee24 - just a heads up that I will be on vacation for a few weeks. I won't have a chance to respond before then, but I will take a look after I get back. Thanks!

nbarbettini avatar Nov 01 '16 16:11 nbarbettini

Any advice on this? how to use SignInManager or UserMAnager in GetIdentity function, as it should be a static function?

bmazaheri avatar Dec 05 '16 09:12 bmazaheri

In Startup.Auth.cs: private void ConfigureAuth(IApplicationBuilder app) { _userManager = app.ApplicationServices.GetRequiredService<UserManager<User>>(); _signInManager = app.ApplicationServices.GetRequiredService<SignInManager<User>>(); }

private async Task<ClaimsIdentity> GetIdentity(string username, string password) {

        var result = await _signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            var user = await _userManager.FindByNameAsync(username);
            var claims = await _userManager.GetClaimsAsync(user);

            return (new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { }));
        }
        return null;

}

anhlee24 avatar Dec 05 '16 09:12 anhlee24

it doesn't work (((((( it works exactly to change the password

_userManager.FindByNameAsync(username).Result; always produces one and the same PasswordHash var result1 = _userManager.CheckPasswordAsync(user, password).Result; var result2 = await _signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);

after changing the password always give FALSE

if you will check at the same time the implementation of conventional controller - login is fine!

finally test (in controller api return _userManager.CheckPasswordAsync(user, password) ) : image

any idea how to fix it?

Vaylandt avatar Dec 28 '16 14:12 Vaylandt

@Vaylandt yes, i am try to find a solution, have you resolve it?

anhlee24 avatar Jan 03 '17 10:01 anhlee24

I am using these just fine and was able to get them working in partial Startup.Auth.cs.

Took some playing around but in your main Startup.cs, those services UserManager, RoleManager, SignInManager etc are available for DI in the main Configure() method.

I just pass them through to my partial Startup.Auth.cs ConfigureAuth() method and set as privates meaning they can be used by the GetIdentity() method.

ConfigureAuth(app, signInManager, userManager, roleManager);

Would like to know if what I've done is the right or wrong approach?

sicollins avatar Jan 03 '17 14:01 sicollins

@sicollins The IdentityDBContext will be initialize as Singleton or Static, so the UserManager, SignInManager will get the old context. ex: if you update password, the signInManager will use the old password to validate instead of new password.

anhlee24 avatar Jan 03 '17 16:01 anhlee24

@anhlee24 I'm not sure you are right that IdentityDbContext is registered as Singleton or static - without seeing your code.

In my case I'm using this standard code: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), null));

which I believe by default is scoped as are the stores i.e. created once per request. See here https://github.com/aspnet/EntityFramework/issues/4988 or https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection#service-lifetimes-and-registration-options

sicollins avatar Jan 03 '17 22:01 sicollins

@sicollins Sorry, i mean if you pass those services UserManager, RoleManager, SignInManager by DI in the main Configure() method in Startup.cs or Startup.Auth.cs. Those service will be singleton instance.

anhlee24 avatar Jan 04 '17 02:01 anhlee24

@anhlee24 I'm not sure you are right, I think they are scoped by default when you use:

services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();

See here in the source: https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs

I'm not sure exactly what your workflow is or how you are using the tokens but once a user logs in they are given a token, from that point on the token is validated on each request through Unprotect method of CustomJwtDataFormat. Are you doing validation here checking their password?

If user changes their password whilst they are logged in (ie has a valid token) then they would need to be issued with a new token as their credentials have changed.

Can you make a public repo with extracts of your code so myself and others reading this can see exactly what you are trying to achieve?

sicollins avatar Jan 04 '17 10:01 sicollins

@anhlee24 , @sicollins , I haven't found a solution yet :(

  1. Create new project vs Identity
  2. Integrate this.SimpleTokenProvider (I hope you understand what I meant project from the repository (this.) :) )
  3. Register new user by WEB (default Home -> Register )
  4. The most difficult: change the code below so that it worked after changing the password of the user private Task<ClaimsIdentity> GetIdentity(string username, string password) { var user = _userManager.FindByNameAsync(username).Result; var result = _userManager.CheckPasswordAsync(user, password).Result; if (result) { return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new[] { new Claim("user_name", user.UserName), new Claim("user_id", user.Id) })); } return Task.FromResult<ClaimsIdentity>(null);

}

  1. Use Fiddler to send a username and password and obtain a access_token

  2. In AccountController.cs (by default created) add simple code: WARNING: DO NOT USE IN PRODUCTIONS!!! VERY UNSECURE!!! [HttpGet] [AllowAnonymous] public IActionResult ChangePass(string username, string newpass) { ApplicationUser user = _userManager.FindByNameAsync(username).Result; if (user != null) { var t = _userManager.GeneratePasswordResetTokenAsync(user).Result; var e = _userManager.ResetPasswordAsync(user, t, newpass).Result; var test = _userManager.CheckPasswordAsync(user, newpass).Result;

         }
         return View();
     }
    
  3. Change Password use AccountController.cs

  4. Use Fiddler to send a username and password and obtain a access_token you will be surprised! using the new username and password method see paragraph 4: var result = _userManager.CheckPasswordAsync(user, password).Result; always gives false

Vaylandt avatar Jan 05 '17 04:01 Vaylandt

RESOLVED!

image

image

Thanks) https://github.com/aspnet/Identity/issues/1065#issuecomment-270573275

Vaylandt avatar Jan 06 '17 06:01 Vaylandt

My solution for injection SignInManager in Startup.cs is the following: `var signInManager = app.ApplicationServices.GetService<SignInManager<IdentityUser>>();

        app.UseSimpleTokenProvider(new TokenProviderOptions
        {
            Path = "/api/authenticate",
            Audience = "ExampleAudience",
            Issuer = "ExampleIssuer",
            SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
            IdentityResolver = (username, password) => GetIdentity(signInManager, username, password)
        });`

I.e. I use IdentityResolver = (username, password) => GetIdentity(signInManager, username, password) instead of IdentityResolver = GetIdentity(signInManager, username, password). What do you think? It seems simplier than using CreateScope() ...

I just checked password changing using Postman. Password changing works

osya avatar Feb 15 '17 06:02 osya

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?tabs=aspnetcore2x

Per-request dependencies Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors are not shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection. For example:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

Now you have to pass the all scoped services as parameters of the Invoke method. I hate those arbitrary changes.

viniciap avatar Mar 14 '18 01:03 viniciap