You 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:
//Generate totp code
var totpCode = TotpService.GenerateCode(user.SecurityStamp.ToString(), modifier);
//Verify totp code
var isValid = TotpService.ValidateCode(
user.SecurityStamp.ToString(),
totpCode,
modifier,
_userOption.TwoFactorTotpExpiration);
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);