10
votes

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.

3
Not sure if this carries over from older version but you need to set the machine keys so they encrypt the tokens the same wayjohnny 5
Core doesn't appear to have a concept of machine keys (at least not in the web.config).Eric
@Eric did you ever figure this out? I have the exact issue happeninguser1015196

3 Answers

4
votes

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.

4
votes

Have you tried setting the application name to the same value in both applications?

services.AddDataProtection().SetApplicationName("same for both apps");

https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview

P.S - I'm struggling with exactly the same problem.

0
votes

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