Genarate Totp Code library.
$ dotnet add package TotpGeneratorYou can generate Totp code based on time, UserSecurityStamp and UserIdentifier and ExpirationTime.
You can use this package instead of Rfc6238AuthenticationService class for generating totp code with identity.
<PackageReference Include="TotpGenerator" Version="1.0.2" />
Usage:
public async ValueTask<int> GetTwoFactorTotpCode(string email)
{
var user = await GetUserByEmail(email);
var modifier = GetModifier(user, TotpEnum.TwoFactor);
var totpCode = TotpService.GenerateCode(user.SecurityStamp.ToString(), modifier);
return totpCode;
}
public async ValueTask<bool> ValidateTwoFactorTotpCode(string email, int totpCode)
{
var user = await GetUserByEmail(email);
string modifier = GetModifier(user, TotpEnum.TwoFactor);
var isValid = TotpService.ValidateCode(
user.SecurityStamp.ToString(),
totpCode,
modifier,
_userOption.TwoFactorTotpExpiration);
return isValid;
}
If you want to modify the TOTP code method generated by Identity, you need to follow these steps:
First, create a class that inherits from the TotpSecurityStampBasedTokenProvider class. Then, you should override the GenerateAsync and ValidateAsync methods and use TotpService instead of Rfc6238AuthenticationService.
GenerateAsync method:
public override async Task<string> GenerateAsync(
string purpose,
UserManager<ApplicationUser> manager,
ApplicationUser user)
{
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
var token = await manager.CreateSecurityTokenAsync(user);
var modifier = await GetUserModifierAsync(purpose, manager, user);
return TotpService.GenerateCode(token, modifier).ToString("D6", CultureInfo.InvariantCulture);
}
ValidateAsync method:
public override async Task<bool> ValidateAsync(
string purpose,
string token,
UserManager<ApplicationUser> manager,
ApplicationUser user)
{
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
int code;
if (!int.TryParse(token, out code))
{
return false;
}
var securityToken = await manager.CreateSecurityTokenAsync(user);
var modifier = await GetUserModifierAsync(purpose, manager, user);
return securityToken != null && TotpService.ValidateCode(securityToken, code, modifier, _userOption.TwoFactorTotpExpiration);
}
Then, introduce the created class to Identity:
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<IdentityContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<CustomTotpSecurityStampBasedTokenProvider >("CustomTotp");
And when generating and validating the code, you should send its provider name to the UserManager:
var verificationCode = await _userManager.GenerateTwoFactorTokenAsync(user, "CustomTotp");
var verify = await _userManager.VerifyTwoFactorTokenAsync(user, "CustomTotp", request.VerificationCode);