Lack of multi-factor authentication - Ruby

Lack of multi-factor authentication - Ruby

Need

Implementation of multi-factor authentication for critical services

Context

  • Usage of Ruby 2.7.3 as the programming language for development
  • Usage of Rails framework for building web applications

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

  def destroy
    session[:user_id] = nil
    redirect_to root_url, notice: "Logged out!"
  end
end
        
        

This code represents a basic login mechanism in a Ruby on Rails application. The SessionsController is responsible for handling the creation and destruction of sessions, which are used to keep track of logged in users.

In the create action, a user is found by their email address and if the user exists and the provided password is correct, the user's ID is stored in the session, effectively logging them in. If the credentials are incorrect, an error message is displayed.

The destroy action simply removes the user ID from the session, effectively logging the user out.

The vulnerability here lies in the fact that there is no multi-factor authentication mechanism in place. If an attacker is able to compromise a user's account, perhaps through a phishing attack or by guessing a weak password, they will be able to log in as that user and access any resources that user has access to.

This is a significant security risk, especially if the compromised account has access to sensitive data or administrative functions. To mitigate this risk, a multi-factor authentication mechanism should be implemented. This could involve sending a verification code to the user's phone or email address, or asking for a biometric identifier, like a fingerprint or face scan.

Steps

  • Implement a multi-factor authentication mechanism for critical services.
  • Use a reliable and secure multi-factor authentication method, such as SMS verification, email verification, or hardware tokens.
  • Ensure that the multi-factor authentication is enforced for accessing sensitive resources, such as databases, shared resources, and web services.
  • Modify the 'create' method in the 'SessionsController' to include the multi-factor authentication check before setting the session.
  • Update the 'create' method to handle the multi-factor authentication process and validate the user's credentials.
  • Update the 'destroy' method in the 'SessionsController' to invalidate the session and log the user out.
  • Test the multi-factor authentication implementation thoroughly to ensure its effectiveness and reliability.

Compliant code

        class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])
    if user && user.authenticate(params[:password])
      # Generate and send OTP for multi-factor authentication
      otp = SecureRandom.hex(3)
      user.update(otp: otp)
      UserMailer.with(user: user).send_otp_email.deliver_later
      
      # Redirect to OTP verification page
      redirect_to verify_otp_path, notice: "Please verify your OTP sent to your email!"
    else
      flash.now[:alert] = "Email or password is invalid"
      render "new"
    end
  end

  def verify_otp
    user = User.find_by(email: params[:email])
    if user && user.otp == params[:otp]
      session[:user_id] = user.id
      redirect_to root_url, notice: "Logged in!"
    else
      flash.now[:alert] = "OTP is invalid"
      render "verify_otp"
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, notice: "Logged out!"
  end
end
        
        

The updated code introduces a multi-factor authentication mechanism to the login process.

In the create method, after the user's email and password are authenticated, a One-Time Password (OTP) is generated using SecureRandom.hex(3). This OTP is then stored in the user's record and sent to the user's email using UserMailer.with(user: user).send_otp_email.deliver_later. The user is then redirected to the OTP verification page with a notice to verify the OTP sent to their email.

A new verify_otp method is introduced. This method retrieves the user's record using the email provided and compares the OTP stored in the user's record with the OTP provided by the user. If they match, the user's ID is stored in the session and the user is redirected to the root URL with a notice that they are logged in. If the OTPs do not match, the OTP verification page is rendered again with an alert that the OTP is invalid.

The destroy method remains unchanged. It simply sets the user ID in the session to nil and redirects the user to the root URL with a notice that they are logged out.

This implementation of multi-factor authentication adds an additional layer of security to the login process, making it more difficult for an attacker who has compromised a user's account to access sensitive resources.

References