Skip to main content

Account lockout

Need

Prevention of unauthorized account lockouts and ensuring continuous user access

Context

  • Usage of Ruby 2.6.0 as the programming language for development
  • Usage of Ruby on Rails for web application development

Description

Non compliant code

class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
end

The above code is a simple Ruby on Rails controller for managing user sessions. In the create action, it finds the user by the email provided in the form parameters and checks if the user exists and the password is correct. If the authentication is successful, it sets the user_id in the session and redirects the user to the root URL with a success message. If the authentication fails, it sets a flash message and re-renders the login form.

However, this code is vulnerable to account lockout attacks. An attacker could repeatedly submit the login form with a valid email but incorrect password, causing the account to be locked out and preventing the legitimate user from logging in. This is because there is no mechanism in place to limit the number of failed login attempts. This vulnerability can cause a denial of service (DoS) if an attacker decides to lock out multiple or all accounts in the system.

Steps

  • Implement account lockout mechanism
  • Limit the number of login attempts
  • Track the number of failed login attempts
  • Lock the account after a certain number of failed attempts
  • Notify the user about the account lockout
  • Provide a way for the user to unlock their account
  • Consider implementing additional security measures like CAPTCHA

Compliant code

class SessionsController < ApplicationController
MAX_LOGIN_ATTEMPTS = 5

def create
user = User.find_by(email: params[:email])

if user && user.authenticate(params[:password])
reset_failed_attempts(user)
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
user.increment!(:failed_attempts) if user
check_failed_attempts(user)
flash.now.alert = "Email or password is invalid"
render "new"
end
end

private

def reset_failed_attempts(user)
user.update(failed_attempts: 0)
end

def check_failed_attempts(user)
if user.failed_attempts >= MAX_LOGIN_ATTEMPTS
flash.now.alert = "Your account has been locked due to too many failed login attempts. Please contact support to unlock your account."
render "new" and return
end
end
end

The updated code introduces a mechanism to limit the number of login attempts and lock the user account after a certain number of failed attempts.

The MAX_LOGIN_ATTEMPTS constant is used to define the maximum number of failed login attempts allowed before the account is locked.

In the create method, if the user authentication fails, the failed_attempts counter for the user is incremented. The check_failed_attempts method is then called to check if the user has exceeded the maximum number of allowed failed login attempts. If the maximum limit is reached, an alert message is displayed to the user indicating that their account has been locked and they need to contact support to unlock it.

If the user authentication is successful, the reset_failed_attempts method is called to reset the failed_attempts counter for the user back to 0.

This approach helps to mitigate the risk of account lockout vulnerability by limiting the number of failed login attempts and locking the account when the limit is exceeded.

References