Reputation: 1847
I have two servers that are using the same ASP.NET Core Identity backend. I generate the password reset token with the following:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
I send this token via an email link. When the user clicks the link, they are taken to a separate site which should provide a UI to change the password. The following code handles the user's password submission of both the token and their new password:
var identityResult = await _userManager.ResetPasswordAsync(applicationUser, code, password);
On the second server, the identity result always returns false because "invalid token".
Looking through the source, I see that the token is generated using the IP address (so I understand why the token validation failed).
My question is how do I enable successful token creation/validation across different machines? In previous forms of ASP.NET, I would likely use a shared machine key to prevent these scenarios. ASP.NET Core doesn't seem to have a similar concept. From what I've read, it seems that this might be a scenario to use the DataProtection API. Unfortunately, I haven't seen any examples as how to apply this to generating the reset token.
Upvotes: 10
Views: 5577
Reputation: 51
Have you tried setting the application name to the same value in both applications?
services.AddDataProtection().SetApplicationName("same for both apps");
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
P.S - I'm struggling with exactly the same problem.
Upvotes: 5
Reputation: 655
you should encode your token before you send it. You should do something like this:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
var encodedCode = HttpUtility.UrlEncode(token);
After encoding it, you must pass the encoded token rather than the generated token.
Upvotes: 4
Reputation: 937
I faced the similar problem. Its not about 2 servers actually. Its about identity framework. You can derived from usermanager and you can override provider with central one. But I tried something different and it worked. First of all ConfirmEmail method looks into database, if you have one database the shouldn't be a problem between tokens with more than one server.
In your usermanager you should create dataprovider at your constructor like this.
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
var dataProtectorProvider = Startup.DataProtectionProvider;
var dataProtector = dataProtectorProvider.Create("My Identity");
this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(dataProtector);
//this.UserTokenProvider.TokenLifespan = TimeSpan.FromHours(24);
}
Also you should be see token in your database table for users. After this line of code.
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
UserManager.EmailService = new EmailService();
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
When you see token in your database, check email for same. then click you callback url and correct the encode of url.
For using dataProtectorProvider ;
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
}
}
Upvotes: 0