traveler3468
traveler3468

Reputation: 1656

ASP.Net Core Identity ResetPasswordAsync Failed : InvalidToken

I'm trying to create a function that will reset the users password when requested in asp .net core 3.1 using Identity

So far whenever the function GeneratePasswordResetTokenAsync is called it returns the token as expected

// Generate Reset Token.
var token = await userManager.GeneratePasswordResetTokenAsync(appIdentityUser);

CfDJ8OHfQcgoimpKvwzwyqjhuwNuJOOwPXPw2F9wg5t7HNMc+YZbnJn1n8cVwBmq/yYV4edV8wl+p6QHSOv/gtW6yat7iuD9v9dBqTmw+Lie2UY9MDLsMEu+GQWaRlUWEH70FoyGqUUcU1/Tzk6tmBvz8cRPlx2KTnJfVc73e1XMZg69pUk58XRuKzRTgwyw/70aSSy6oh1LgDj4g1OqPRSqsgKaPh1vUnMThYb0GwqovqGZoU37N5COem4RmYFn4uVIEQ==

For testing purposes I am now calling the ResetPasswordAsync within the same method to see if the password will reset

NOTE user is found.

// Find User.
AppIdentityUser user = await userManager.FindByEmailAsync(appIdentityUser.Email);

// Attempt To Reset The Password To someRealL0ngP@ssW0rd
IdentityResult resetPassword = await userManager.ResetPasswordAsync(user, token, "someRealL0ngP@ssW0rd");

Unfortunately I receive this error Failed : InvalidToken

Here is AddIdentity that is in the startup file along with the DbContext.

...
// Create The DbContext.
services.AddDbContext<AppIdentityDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("AussieFoods2ULocal"));
});

// Identity User. Plus Password Complexity For Easy Testing.
services.AddIdentity<AppIdentityUser, IdentityRole>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 5;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequiredUniqueChars = 0;
    options.Password.RequireUppercase = false;
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();

 // Paths For The Identity
 services.ConfigureApplicationCookie(options =>
 {
     options.LoginPath = "/Security/SignIn";
     options.AccessDeniedPath = "/Security/AccessDenided";
 });
    
 services.Configure<DataProtectionTokenProviderOptions>(options =>
 {
      options.TokenLifespan = TimeSpan.FromHours(2);
 });

 services.AddAuthentication();
 ...

I'm not sure if this has anything to do with it, but I did create a custom class that inherits from IdentityUser

public class AppIdentityUser : IdentityUser
{
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    
    [EmailAddress]
    [Required]
    public override string Email { get => base.Email; set => base.Email = value; }

    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string Suburb { get; set; }
    public string City { get; set; }
    public string PostCode { get; set; }
}

Upvotes: 2

Views: 3398

Answers (4)

Sameer Tanveer
Sameer Tanveer

Reputation: 11

I encountered the issue while configuring MFA during first login, where I offered users the choice of authentication methods: Email, SMS, or Authenticator App. The issue occurred only when the Authenticator App was selected.

After investigation, we found that configuring the Authenticator App resets the SecurityStamp for the user in the AspNetUsers table. So, any tokens generated before this reset are rendered invalid.

To resolve the issue, we adjusted our flow as follows:

Check if the Authenticator App is configured: Before proceeding with actions like password reset, confirm whether the user has completed MFA setup with the Authenticator App.

Generate a new token: After the Authenticator App is set up, create a fresh token and validate the user with this token.

Upvotes: 0

Kidd
Kidd

Reputation: 97

If you're sending an email with something like Sendgrid and using HtmlContent to link the token, I found that all instances of "+" are replaced with " " for whatever reason.

The simple solution to this is to do a string.Replace.

var decodedToken = token.Replace(" ", "+");

Upvotes: 4

Farhad Zamani
Farhad Zamani

Reputation: 5861

You can create an custom token provider to generate PasswordResetToken like this

public class ResetPasswordTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
    public ResetPasswordTokenProvider(IDataProtectionProvider dataProtectionProvider,
        IOptions<ResetPasswordTokenProviderOptions> options)
        : base(dataProtectionProvider, options)
    {
    }
}
public class ResetPasswordTokenProviderOptions : DataProtectionTokenProviderOptions
{
    public ResetPasswordTokenProviderOptions()
    {
        Name = "ResetPasswordDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromDays(1);
    }
}

Then register custom token provider in AddIdentity and services

services.AddIdentity<AppIdentityUser, IdentityRole>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 5;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequiredUniqueChars = 0;
    options.Password.RequireUppercase = false;
    options.User.RequireUniqueEmail = true;
    //NOTE THIS
    options.Tokens.ProviderMap.Add("ResetPassword", new TokenProviderDescriptor(typeof(ResetPasswordTokenProvider<AppIdentityUser>)));
    options.Tokens.PasswordResetTokenProvider = "ResetPassword";

})
.AddTokenProvider<ResetPasswordTokenProvider<AppUserIdentity>>("ResetPassword")//<--NOTE THIS
.AddEntityFrameworkStores<AppIdentityDbContext>();

Upvotes: 1

Shoejep
Shoejep

Reputation: 4839

To test your Startup.cs config, I quickly made a new project. The ConfigureServices was the same as yours with services.AddControllersWithViews(); at the end and Configure looks liked below.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

I then created an action to create a user and reset the password. Below is the action to reset the password.

public async Task ResetPassword()
{
    var user = await UserManager.FindByEmailAsync("[email protected]");
    var token = await UserManager.GeneratePasswordResetTokenAsync(user);
    var result = await UserManager.ResetPasswordAsync(user, token, Guid.NewGuid().ToString());
}

This returns a successful result.

Upvotes: 2

Related Questions