11

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?

sridharnetha
  • 2,104
  • 8
  • 35
  • 69
  • Password reset tokens should be hard to guess, therefore, the longer the better. The question I have is why you want a shorter token length? – phuzi Jun 29 '15 at 08:17
  • 4
    @phuzi here i am going to use shorter token for generate a link text then send it to the customer's email address. so that our customer will copy that link text and paste it into other interface instead of click. – sridharnetha Jun 29 '15 at 08:23
  • 1
    chechout my answer here http://stackoverflow.com/a/40961708/4251431 – Basheer AL-MOMANI Dec 05 '16 at 05:40
  • 1
    It blows my mind how Microsoft thinks it's ok to provide us with a 244 character reset token. That is beyond absurd. I'm guessing it's some kind of encrypted claims based token but this kind of a length just leads to cut and paste issues with customers unable to enter the complete link. – Simon_Weaver Feb 05 '18 at 05:06
  • Works fine if you put the url in in a `href` `` tag. – mxmissile Oct 15 '19 at 14:31

5 Answers5

7

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);

Pecan
  • 350
  • 3
  • 14
1

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.

Keith
  • 150,284
  • 78
  • 298
  • 434
  • did you read the OP? "generate password reset token with short length?". There is a method `UserManager.GeneratePasswordResetToken` - how to make it spit smaller token – Toolkit Oct 12 '19 at 14:53
  • @Toolkit I read the OP. You can't make `GeneratePasswordResetToken` _"spit smaller token"_, it generates a fixed length token. You can generate shorter tokens with your own implementation, as I've done here, but the shorter the token the easier it will be to brute force and the more likely collisions will be - you can choose that balance with your own implementation. `GeneratePasswordResetToken` doesn't let you pick, it just assumes you need something high security with a long token. You don't _have_ to use it. – Keith Oct 14 '19 at 17:19
  • you didn't do any implementation of anything here, you just showed how to do a random string. You can use google for that – Toolkit Oct 15 '19 at 09:05
  • And you also suggest that to prevent brute force you need a 244 char long token?? Man where did you get these ideas? – Toolkit Oct 15 '19 at 09:09
  • @Toolkit yes, a random string is fine for token generation, so long as it can't be predicted and you store it somewhere (ideally with a timeout). I didn't suggest anything about Microsoft's 244 char implementation, I don't think that's needed to prevent brute force. At a very rough guess they might possibly be key stretching some hashed value and that's why it's so long, but that's a guess. The OP has asked for a shorter string, and they can make it as short as they like, but shorter = more brute forceable, longer = more secure. There is a balance that depends on their context. – Keith Oct 15 '19 at 12:55
  • the problem is .NET uses UserManager.ResetPassword and you need to feed that monster token into this method. So that's why we are suffering here :) – Toolkit Oct 15 '19 at 14:02
1

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);
Qudus
  • 1,440
  • 2
  • 13
  • 22
0

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);
Toolkit
  • 10,779
  • 8
  • 59
  • 68
  • Minor point in most contexts, but avoid using `Guid.NewGuid()` to generate security tokens, GUIDs look very random, but can be predicted on a lot of hardware. Instead use `System.Security.Cryptography.RandomNumberGenerator.Create()` for a less predictable algorithm. – Keith Oct 15 '19 at 13:00
0

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.

Ryan M
  • 18,333
  • 31
  • 67
  • 74