using System; using System.Globalization; using System.Threading.Tasks; namespace Microsoft.AspNetCore.Identity { /// /// Represents a token provider that generates time based codes using the user's security stamp. /// /// The type encapsulating a user. public abstract class TotpSecurityStampBasedTokenProvider : IUserTwoFactorTokenProvider where TUser : class { /// /// Generates a token for the specified and . /// /// The purpose the token will be used for. /// The that can be used to retrieve user properties. /// The user a token should be generated for. /// /// The that represents the asynchronous operation, containing the token for the specified /// and . /// /// /// The parameter allows a token generator to be used for multiple types of token whilst /// insuring a token for one purpose cannot be used for another. For example if you specified a purpose of "Email" /// and validated it with the same purpose a token with the purpose of TOTP would not pass the check even if it was /// for the same user. /// /// Implementations of should validate that purpose is not null or empty to /// help with token separation. /// public virtual async Task GenerateAsync(string purpose, UserManager manager, TUser user) { if (manager == null) { throw new ArgumentNullException(nameof(manager)); } var token = await manager.CreateSecurityTokenAsync(user); var modifier = await GetUserModifierAsync(purpose, manager, user); return Rfc6238AuthenticationService.GenerateCode(token, modifier).ToString("D6", CultureInfo.InvariantCulture); } /// /// Returns a flag indicating whether the specified is valid for the given /// and . /// /// The purpose the token will be used for. /// The token to validate. /// The that can be used to retrieve user properties. /// The user a token should be validated for. /// /// The that represents the asynchronous operation, containing the a flag indicating the result /// of validating the for the specified and . /// The task will return true if the token is valid, otherwise false. /// public virtual async Task ValidateAsync(string purpose, string token, UserManager manager, TUser 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 && Rfc6238AuthenticationService.ValidateCode(securityToken, code, modifier); } /// /// Returns a constant, provider and user unique modifier used for entropy in generated tokens from user information. /// /// The purpose the token will be generated for. /// The that can be used to retrieve user properties. /// The user a token should be generated for. /// /// The that represents the asynchronous operation, containing a constant modifier for the specified /// and . /// public virtual async Task GetUserModifierAsync(string purpose, UserManager manager, TUser user) { if (manager == null) { throw new ArgumentNullException(nameof(manager)); } var userId = await manager.GetUserIdAsync(user); return "Totp:" + purpose + ":" + userId; } /// /// Returns a flag indicating whether the token provider can generate a token suitable for two factor authentication token for /// the specified . /// /// The that can be used to retrieve user properties. /// The user a token could be generated for. /// /// The that represents the asynchronous operation, containing the a flag indicating if a two /// factor token could be generated by this provider for the specified . /// The task will return true if a two factor authentication token could be generated, otherwise false. /// public abstract Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user); } }