SimpleTokenProvider
SimpleTokenProvider copied to clipboard
How to use UserManager for GetIdentity in Startup.cs class
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();
}
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!
Any advice on this? how to use SignInManager
or UserMAnager
in GetIdentity
function, as it should be a static function?
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;
}
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) ) :
any idea how to fix it?
@Vaylandt yes, i am try to find a solution, have you resolve it?
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 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 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 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 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?
@anhlee24 , @sicollins , I haven't found a solution yet :(
- Create new project vs Identity
- Integrate this.SimpleTokenProvider (I hope you understand what I meant project from the repository (this.) :) )
- Register new user by WEB (default Home -> Register )
- 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);
}
-
Use Fiddler to send a username and password and obtain a access_token
-
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(); }
-
Change Password use AccountController.cs
-
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
RESOLVED!
Thanks) https://github.com/aspnet/Identity/issues/1065#issuecomment-270573275
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
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.