title: ASP.NET Core 2.2 - Password reset not working on Azure (Invalid token)
body: <p>I have an <code>ASP.NET Core</code> 2.2 application running on multiple instances on an Azure Web App; it uses <code>EF Core 2.2</code> and <code>ASP.NET Identity</code>.</p>
  
  <p>Everything works fine except the Password Reset flow where a user receives a link with token per e-mail and needs to choose a new password by clicking on that link. It works perfectly locally, but <strong>on Azure it always fails with an "Invalid Token" error</strong>.</p>
  
  <p>The tokens are HTML encoded and decoded as necessary; and I have checks in place to ensure they match those on the database; URL encoding is not the issue.</p>
  
  <p>I've configured <code>DataProtection</code> to store the keys to an Azure Blob storage, but to no avail. 
  <strong>The keys are stored  in the blob store all right, but I still get an "Invalid Token" error</strong>.</p>
  
  <p>Here's my set up on <code>Startup.cs</code>:</p>
  
  <pre class="lang-cs prettyprint-override"><code>public void ConfigureServices(IServiceCollection services)
  {
      // This needs to happen before "AddMvc"
      // Code for this method shown below
      AddDataProtecion(services);
  
      services.AddDbContext&lt;MissDbContext&gt;(options =&gt; options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  
      var sp = services.BuildServiceProvider();
  
      services.ConfigureApplicationCookie(x =&gt;
      {
          x.Cookie.Name = ".MISS.SharedCookie";
          x.ExpireTimeSpan = TimeSpan.FromHours(8);
          // We need to set the cookie's DataProtectionProvider to ensure it will get stored in the azure blob storage
          x.DataProtectionProvider = sp.GetService&lt;IDataProtectionProvider&gt;();
      });
  
      services.AddIdentity&lt;ApplicationUser, ApplicationRole&gt;()
          .AddEntityFrameworkStores&lt;MissDbContext&gt;()
          .AddDefaultTokenProviders();
  
  
      // https://tech.trailmax.info/2017/07/user-impersonation-in-asp-net-core/
      services.Configure&lt;SecurityStampValidatorOptions&gt;(options =&gt; 
      {
          options.ValidationInterval = TimeSpan.FromMinutes(10);
          options.OnRefreshingPrincipal = context =&gt;
          {
              var originalUserIdClaim = context.CurrentPrincipal.FindFirst("OriginalUserId");
              var isImpersonatingClaim = context.CurrentPrincipal.FindFirst("IsImpersonating");
              if (isImpersonatingClaim?.Value == "true" &amp;&amp; originalUserIdClaim != null)
              {
                  context.NewPrincipal.Identities.First().AddClaim(originalUserIdClaim);
                  context.NewPrincipal.Identities.First().AddClaim(isImpersonatingClaim);
              }
              return Task.FromResult(0);
          };
      });
  
       // some more initialisations here
  }
  </code></pre>
  
  <p>And here is the <code>AddDataProtection</code> method:</p>
  
  <pre class="lang-cs prettyprint-override"><code>/// &lt;summary&gt;
  /// Add Data Protection so that cookies don't get invalidated when swapping slots.
  /// &lt;/summary&gt;
  /// &lt;param name="services"&gt;&lt;/param&gt;
  /// &lt;returns&gt;&lt;/returns&gt;
  void AddDataProtecion(IServiceCollection services)
  {
      var sasUrl = Configuration.GetValue&lt;string&gt;("DataProtection:SaSUrl");
      var containerName = Configuration.GetValue&lt;string&gt;("DataProtection:ContainerName");
      var applicationName = Configuration.GetValue&lt;string&gt;("DataProtection:ApplicationName");
      var blobName = Configuration.GetValue&lt;string&gt;("DataProtection:BlobName");
      var keyIdentifier = Configuration.GetValue&lt;string&gt;("DataProtection:KeyVaultIdentifier");
  
      if (sasUrl == null || containerName == null || applicationName == null || blobName == null)
          return;
  
      var storageUri = new Uri($"{sasUrl}");
  
      var blobClient = new CloudBlobClient(storageUri);
  
      var container = blobClient.GetContainerReference(containerName);
      container.CreateIfNotExistsAsync().GetAwaiter().GetResult();
  
      applicationName = $"{applicationName}-{Environment.EnvironmentName}";
      blobName = $"{applicationName}-{blobName}";
  
      services.AddDataProtection()
          .SetApplicationName(applicationName)
          .PersistKeysToAzureBlobStorage(container, blobName);
  }
  </code></pre>
  
  <p>I've also tried persisting the keys to the DbContext, but the result is the same: keys are stored, but I still get an<code>Invalid token</code> message when attempting a password reset, Every. Single. Time.</p>
  
  <h3>the Request Password Reset method</h3>
  
  <pre class="lang-cs prettyprint-override"><code>public async Task RequestPasswordReset(string emailAddress, string ip, Request httpRequest) 
  {
      var user = await _userManager.FindByEmailAsync(emailAddress);
  
      var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
  
      var resetRequest = new PasswordResetRequest
      {
          CreationDate = DateTime.Now,
          ExpirationDate = DateTime.Now.AddDays(1),
          UserId = user.Id,
          Token = resetToken,
          IP = ip
      };
  
      _context.PasswordResetRequests.Add(resetRequest);
      await _context.SaveChangesAsync();
  
      await SendPasswordResetEmail(user, resetRequest, httpRequest);
  }
  </code></pre>
  
  <h3>The Reset password method</h3>
  
  <p>Once the user requests a password reset, they receive an e-mail with a link and a token; here's how I attempt to reset the user's password after the user clicks on that link:</p>
  
  <pre class="lang-cs prettyprint-override"><code>public async Task&lt;IdentityResult&gt; ResetPassword(string token, string password) 
  {
      // NO PROBLEM HERE - The received token matches with the one in the Db
      var resetRequest = await _context.PasswordResetRequests
          .AsNoTracking()
          .FirstOrDefaultAsync(x =&gt; x.Token == token);
  
      var user = await _userManager.FindByIdAsync(resetRequest.UserId);
  
      // PROBLEM - This method returns "Invalid Token"
      var result = await _userManager.ResetPasswordAsync(user, resetRequest.Token, password);
  
      if (result.Succeeded)
          await SendPasswordChangedEmail(user);
  
      return result;
  }
  </code></pre>
  
  <p>As I state in the code comments, the token received in the request matches the one generated in the database, but  <code>ResetPasswordAsync</code> does it's own token validation, and that fails.</p>
  
  <p>Any help would still be appreciated</p>