Account lockout - Ruby

Account lockout - Ruby

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