I am using Asp.Net Identity for generate a password reset token.
string Token = userManager.GeneratePasswordResetToken(userId);
above code is giving me a token with large length. Is it possible to generate password reset token with short length?
I am using Asp.Net Identity for generate a password reset token.
string Token = userManager.GeneratePasswordResetToken(userId);
above code is giving me a token with large length. Is it possible to generate password reset token with short length?
You can use TotpSecurityStampBasedTokenProvider
to generate 6-digit number:
public class ResetPasswordTokenProvider : TotpSecurityStampBasedTokenProvider<OriIdentityUser>
{
public const string ProviderKey = "ResetPassword";
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<OriIdentityUser> manager, OriIdentityUser user)
{
return Task.FromResult(false);
}
}
And in the startup class add:
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
options.Tokens.PasswordResetTokenProvider = ResetPasswordTokenProvider.ProviderKey;
})
.AddDefaultTokenProviders()
.AddTokenProvider<ResetPasswordTokenProvider>(ResetPasswordTokenProvider.ProviderKey);
Your password reset token needs to be cryptographically random - that means that given a set of used tokens the next token should be impossible to guess. It also needs to cover a large enough set of possible values that brute force attempting all of them is impractically slow.
You can make changes to make the latter work with smaller sets of possible values - for instance you can add a 1s delay to the password reset page/action before checking the token. Your users will barely notice the delay on the rarely used page, but attackers will not be able to attempt lots of tokens quickly.
So, first you need to get a cryptographically random number:
var random = new byte[8];
using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
rng.GetBytes(random);
I've put 8
bytes here but you can make this any length you want.
Next you need to make this into a nice copy-pasteable string. You can do that with a unicode conversion but I find base 64 more reliable:
Convert.ToBase64String(random).TrimEnd('=');
Using this with 8
bytes will give you 64 bits of possible values and a 10 char string. Using 4
will give you 32 bits (probably enough with slow token checking on a low security site) and a 5 char string.
I have an ideal solution built on the foundation of the decent answers found here. First, you have to inject IMemoryCache
in the class in question.
public class ResetController : ControllerBase
{
private readonly IMemoryCache _memoryCache;
public ResetController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
}
Second, memory cache allows you to create a key-value pair relationship stored in cache so that you can retreive the value with the key later. In this case, the value is the actual generated token and the key is the variable truncatedtoken
.
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
string tokentruncated;
using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
{
byte[] tokenData = new byte[32];
rng.GetBytes(tokenData);
tokentruncated = Convert.ToBase64String(tokenData);
}
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromDays(1));
_memoryCache.Set(tokentrunc, token, cacheEntryOptions);
Once the confirmation token (truncatedtoken
) is sent and the user is ready to reset the password. The truncatedtoken
key is used to retrieve the actual token.
string token;
_memoryCache.TryGetValue(truncatedtoken, out token);
var result = await _userManager.ResetPasswordAsync(user, token, "New password");
Instead of using TryGetValue
, you could also retreive the token with;
string token = _memoryCache.Get<string>(truncatedtoken);
Replace the long code with a shorter one and store in in app cache.
When generating:
var token = UserManager.GeneratePasswordResetToken(user.Id);
var guidCode = GenerateCustomToken(); //use this https://stackoverflow.com/a/1668371/631527
CacheHelper.AddToCache(guid, token); //add it to MemoryCache.Default
var resetUrl = $"https://.....com/password-reset/{guidCode}/{userName}";
When checking:
//get guidCode from the request in your GET or POST controller
var token = CacheHelper.GetValue(guidCode); //retrieve it from cache
var result = UserManager.ResetPassword(user.Id, token, model.Password);
One thing would be to generate your own 8-digit GUID and send that to the user and have your own look up table to get the real token.