/// <summary> /// /// </summary> /// <param name="purpose"></param> /// <param name="token"></param> /// <param name="manager"></param> /// <param name="user"></param> /// <returns></returns> public virtual async Task <bool> ValidateAsync(string purpose, string token, UserManager <TUser> manager, TUser user) { var key = await manager.GetAuthenticatorKeyAsync(user); int code; if (!int.TryParse(token, out code)) { return(false); } var hash = new HMACSHA1(Base32.FromBase32(key)); var unixTimestamp = Convert.ToInt64(Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds)); var timestep = Convert.ToInt64(unixTimestamp / 30); // Allow codes from 90s in each direction (we could make this configurable?) for (int i = -2; i <= 2; i++) { var expectedCode = Rfc6238AuthenticationService.ComputeTotp(hash, (ulong)(timestep + i), modifier: null); if (expectedCode == code) { return(true); } } return(false); }
/// <summary> /// Generates a token for the specified <paramref name="user"/> and <paramref name="purpose"/>. /// </summary> /// <param name="purpose">The purpose the token will be used for.</param> /// <param name="manager">The <see cref="UserManager{TUser}"/> that can be used to retrieve user properties.</param> /// <param name="user">The user a token should be generated for.</param> /// <returns> /// The <see cref="Task"/> that represents the asynchronous operation, containing the token for the specified /// <paramref name="user"/> and <paramref name="purpose"/>. /// </returns> /// <remarks> /// The <paramref name="purpose"/> 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 <see cref="IUserTwoFactorTokenProvider{TUser}"/> should validate that purpose is not null or empty to /// help with token separation. /// </remarks> public virtual async Task <string> GenerateAsync(string purpose, UserManager <TUser> 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)); }
/// <summary> /// Returns a flag indicating whether the specified <paramref name="token"/> is valid for the given /// <paramref name="user"/> and <paramref name="purpose"/>. /// </summary> /// <param name="purpose">The purpose the token will be used for.</param> /// <param name="token">The token to validate.</param> /// <param name="manager">The <see cref="UserManager{TUser}"/> that can be used to retrieve user properties.</param> /// <param name="user">The user a token should be validated for.</param> /// <returns> /// The <see cref="Task"/> that represents the asynchronous operation, containing the a flag indicating the result /// of validating the <paramref name="token"> for the specified </paramref><paramref name="user"/> and <paramref name="purpose"/>. /// The task will return true if the token is valid, otherwise false. /// </returns> public virtual async Task <bool> ValidateAsync(string purpose, string token, UserManager <TUser> 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)); }