Reputation: 53
I have "Forgot Password" page in my project. At this page, I create 6 digit number by using _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");
then send this code to user. Now I want to set time span for this code that expire it after 2 minutes. I add a custom provider to the service container but It doesn't work for the code that created by GenerateUserTokenAsync
.and also I don't want to set timeSpan that apply to all token. How can I set custom timeSpan for this?
this my ForgotPassword method:
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
return RedirectToPage("./ForgotPasswordConfirmation");
}
//create 6 digit code and use it in send sms
var code = await _userManager
.GenerateUserTokenAsync(user,
TokenOptions.DefaultPhoneProvider,
"ResetPasswordTokenPurpose");
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
sendSms(code);
return RedirectToPage("./ForgotPasswordConfirmation");
}
return Page();
}
this is ResetPassword method:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
//check validate 6 digit code
var tokenVerified = await _userManager
.VerifyUserTokenAsync(user,
TokenOptions.DefaultPhoneProvider,
"ResetPasswordTokenPurpose",
Input.Code);
if (!tokenVerified)
return Page();
//new token for reseting password
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var result = await _userManager.ResetPasswordAsync(user, token, Input.Password);
if (result.Succeeded)
{
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
Change the ResetPassword token lifespan:
public class CustomResetPasswordTokenProvider<TUser>
: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomResetPasswordTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<ResetPasswordTokenProviderOptions> options)
: base(dataProtectionProvider, options)
{
}
}
public class ResetPasswordTokenProviderOptions
: DataProtectionTokenProviderOptions
{
public ResetPasswordTokenProviderOptions()
{
Name = "ResetPasswordProtectorTokenProvider";
TokenLifespan = TimeSpan.FromMinutes(2);
}
}
call it in StartUp.cs:
services
.AddDefaultIdentity<IdentityUser>(config =>
{
config.Tokens.ProviderMap.Add("CustomResetPassword",
new TokenProviderDescriptor(
typeof(CustomResetPasswordTokenProvider<IdentityUser>)));
config.Tokens.PasswordResetTokenProvider = "CustomResetPassword";
})
.AddEntityFrameworkStores<ApplicationDbContext>();
services
.AddTransient<CustomResetPasswordTokenProvider<IdentityUser>>();
Upvotes: 0
Views: 1244
Reputation: 387507
config.Tokens.PasswordResetTokenProvider = "CustomResetPassword";
In your configuration, you only set your custom provider for the PasswordResetTokenProvider
. So this configuration only applies to the method pair GeneratePasswordResetTokenAsync
and ResetPasswordAsync
. But you are using these methods only internally to actually perform the password reset using a very short-lived token. So the lifetime limitation here is actually not needed since the token will never leave the application and is used immediately within the same second anyway.
Instead, what you are doing is use the DefaultPhoneProvider
to create the password token that is sent via SMS:
var code = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordTokenPurpose");
Now, the default phone provider uses a TOTP-based implementation, time-based one-time passwords. These passwords are by design time-restricted and not valid for an infinite amount of time. According to spec, the time window, in which a single token is considered valid, is meant to be rather small.
The implementation that is used by the phone provider uses a fixed time window of 3 minutes, and also accepts two additional time windows in each direction to account for time shifts, resulting in a total theoretical range of 15 minutes in which the user has the chance to retrieve the SMS and enter the code.
If you want to further restrict this, you should first consider that there is no guaranteed SMS delivery time, so choosing a too small window can potentially lock out users if the SMS does not deliver on time or if they are not able to enter it quickly enough.
If you do want to go that route though, you can provide your own implementation for the TOTP token provider which uses a different window size. Note that you will have to implement the token generation yourself though since the implementation is internal.
Upvotes: 2