I have a WCF service which should authenticate user against any bruteforce attack.
Below is the code to avoid any bruteforce attack. I have used MemoryCache
to keep track of invalid attempts by a user.
Could you please review the code and let me know if there are any issues?
Following defined in config file:
<InvalidLoginAttemptsThreshold value = "5"/>
<InvalidLoginLockoutInMinutes value = "20"/>
Code to add invalid login to cache and lock the account if invalid logins reaches threshold set in config.
private readonly CacheItemPolicy cacheInvalidLoginPolicy = new CacheItemPolicy();
private readonly ObjectCache cacheInvalidLogins = MemoryCache.Default; //Cache to hold Invalid Login Attempts: {UserId, obj UserLogin}
public class UserLogin
{
public string UserName { get; set; }
public List<DateTime> InvalidLoginTimestamp { get; set; }
public bool IsLocked { get; set; }
}
private bool AuthenticateUser(string userName, string password)
{
bool validCredentials = false;
string userNameUpperCase = userName.ToUpper(); //MemoryCache treats 'a' and 'A' as different names
if (cacheInvalidLogins.Contains(userNameUpperCase) && ((UserLogin)cacheInvalidLogins[userNameUpperCase]).IsLocked)
{
log.ErrorFormat("UserId: {0} tried to access locked account at {1}", userName, DateTime.Now);
throw new FaultException("Your account is locked out. Please try after some time.", new FaultCode("AccountLocked"));
}
try
{
validCredentials = securityService.Authenticate(userName, password);
if (!validCredentials)
{
AddInvalidLoginToCache(userNameUpperCase);
}
else if (validCredentials && cacheInvalidLogins.Contains(userNameUpperCase))
{
//For valid credentials - remove from cache
cacheInvalidLogins.Remove(userNameUpperCase);
}
}
catch (Exception exception)
{
log.Error(exception);
}
return validCredentials;
}
private void AddInvalidLoginToCache(string userName)
{
if (cacheInvalidLogins.Contains(userName))
{
string clientId = userName.Split('\\')[0];
//Increment InvalidLoginCount for this user
UserLogin invalidLogin = (UserLogin)cacheInvalidLogins[userName];
invalidLogin.InvalidLoginTimestamp.Add(DateTime.Now);
cacheInvalidLogins[userName] = invalidLogin; //todo: update cache policy here
//Lock user account if InvalidLoginCount reach threshold set in config
if (invalidLogin.InvalidLoginTimestamp.Count == invalidLoginThresholdFromConfig))
{
invalidLogin.IsLocked = true;
cacheInvalidLogins.Set(userName.ToUpper(), invalidLogin, LockOutCachePolicyFromConfig());
log.ErrorFormat("Invalid Login attempted by UserId: {0} at these timestamps: {1}", userName, String.Join(",", invalidLogin.InvalidLoginTimestamp));
log.ErrorFormat("Account is locked out for UserId: {0}", userName);
throw new FaultException("Your account is locked out. Please try after some time.", new FaultCode("AccountLocked"));
}
}
else
{
//todo: check if extra config setting is required for this cache policy
cacheInvalidLoginPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(5);
cacheInvalidLogins.Add(userName.ToUpper(),
new UserLogin {UserName = userName, InvalidLoginTimestamp = new List<DateTime> {DateTime.Now}},
cacheInvalidLoginPolicy);
}
}
securityService
? \$\endgroup\$