title: ASP.NET Identity verify if ResetPassword token has expired
body: <p>In my API I have 2 endpoints, first that generates email to reset password form (I generate token using <code>UserManager.GeneratePasswordResetTokenAsync</code>).<br>
  Second endpoint is for actual password reset (I use <code>UserManager.ResetPasswordAsync</code>).</p>
  
  <p>My requirement was to verify if token that is required for password reset isn't expired.</p>
  
  <p>Searching over GitHub I found <a href="https://github.com/aspnet/Identity/issues/307#issuecomment-72556469" rel="noreferrer">this issue</a> and from what I found this isn't possible by design.</p>
  
  <p>However searching deeper I've found that <a href="https://www.symbolsource.org/Public/Metadata/NuGet/Project/Microsoft.AspNet.Identity.Core/2.2.1/Release/Default/Microsoft.AspNet.Identity.Core/Microsoft.AspNet.Identity.Core/UserManager.cs" rel="noreferrer">UserManager.ResetPasswordAsync</a> internally uses <code>ValidateAsync</code> from <a href="https://www.symbolsource.org/Public/Metadata/NuGet/Project/Microsoft.AspNet.Identity.Owin/2.2.1/Release/Default/Microsoft.AspNet.Identity.Owin/Microsoft.AspNet.Identity.Owin/DataProtectorTokenProvider.cs?ImageName=Microsoft.AspNet.Identity.Owin" rel="noreferrer">Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider</a></p>
  
  <p>Having this I've created this extension method:</p>
  
  <pre><code>using Microsoft.AspNet.Identity;
  using Microsoft.AspNet.Identity.Owin;
  using System;
  using System.Globalization;
  using System.IO;
  using System.Text;
  
  namespace Api.Extensions
  {
      public enum TokenValidity
      {
          VALID,
          INVALID,
          INVALID_EXPIRED,
          ERROR
      }
  
      public static class UserManagerExtensions
      {
          public static TokenValidity IsResetPasswordTokenValid&lt;TUser, TKey&gt;(this UserManager&lt;TUser, TKey&gt; manager, TUser user, string token) where TKey : IEquatable&lt;TKey&gt; where TUser : class, IUser&lt;TKey&gt;
          {
              return IsTokenValid(manager, user, "ResetPassword", token);
          }
  
          public static TokenValidity IsTokenValid&lt;TUser, TKey&gt;(this UserManager&lt;TUser, TKey&gt; manager, TUser user, string purpose, string token) where TKey : IEquatable&lt;TKey&gt; where TUser : class, IUser&lt;TKey&gt;
          {
              try
              {
                  //not sure if this is needed??
                  if (!(manager.UserTokenProvider is DataProtectorTokenProvider&lt;TUser, TKey&gt; tokenProvider)) return TokenValidity.ERROR;
  
                  var unprotectedData = tokenProvider.Protector.Unprotect(Convert.FromBase64String(token));
                  var ms = new MemoryStream(unprotectedData);
                  using (var reader = ms.CreateReader())
                  {
                      var creationTime = reader.ReadDateTimeOffset();
                      var expirationTime = creationTime + tokenProvider.TokenLifespan;
  
                      var userId = reader.ReadString();
                      if (!String.Equals(userId, Convert.ToString(user.Id, CultureInfo.InvariantCulture)))
                      {
                          return TokenValidity.INVALID;
                      }
  
                      var purp = reader.ReadString();
                      if (!String.Equals(purp, purpose))
                      {
                          return TokenValidity.INVALID;
                      }
  
                      var stamp = reader.ReadString();
                      if (reader.PeekChar() != -1)
                      {
                          return TokenValidity.INVALID;
                      }
  
                      var expectedStamp = "";
                      //if supported get security stamp for user
                      if (manager.SupportsUserSecurityStamp)
                      {
                          expectedStamp = manager.GetSecurityStamp(user.Id);
                      }
  
                      if (!String.Equals(stamp, expectedStamp)) return TokenValidity.INVALID;
  
                      if (expirationTime &lt; DateTimeOffset.UtcNow)
                      {
                          return TokenValidity.INVALID_EXPIRED;
                      }
  
                      return TokenValidity.VALID;
                  }
              }
              catch
              {
                  // Do not leak exception
              }
              return TokenValidity.INVALID;
          }
      }
  
      internal static class StreamExtensions
      {
          internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
  
          public static BinaryReader CreateReader(this Stream stream)
          {
              return new BinaryReader(stream, DefaultEncoding, true);
          }
  
          public static BinaryWriter CreateWriter(this Stream stream)
          {
              return new BinaryWriter(stream, DefaultEncoding, true);
          }
  
          public static DateTimeOffset ReadDateTimeOffset(this BinaryReader reader)
          {
              return new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
          }
  
          public static void Write(this BinaryWriter writer, DateTimeOffset value)
          {
              writer.Write(value.UtcTicks);
          }
      }
  }
  </code></pre>
  
  <p>so now I'm able to add this check:</p>
  
  <pre><code>if (UserManager.IsResetPasswordTokenValid(user, model.Code) == TokenValidity.INVALID_EXPIRED)
  {
      return this.BadRequest("errorResetingPassword", "Link expired");
  }
  </code></pre>
  
  <p>My question are:<br></p>
  
  <p>1.Is there an easier way of doing this?<br>
  My intention is to show user information that link in email has expired, because right now all he can see is that there was problem with resetting password.</p>
  
  <p>2.If there isn't build in method of doing this what are the potential security vulnerabilities? I use my extension method as an additional check. If my method return true I still use <code>ResetPasswordAsync</code>.</p>