Link Search Menu Expand Document

Broken Authorization in Ruby

Vulnerable example

The following Ruby snippet shows a Ruby on Rails controller that pulls the user from the request parameter, looking up the user email passed as a parameter in the URL:

def restricted
  @user = User.find_by(email: params[:email])
  if !(@user)
    flash[:error] = "Sorry, invalid user"
    redirect_to public_index_path
  end
end

This could be abused by any user to access the restricted API by invoking it, passing the email address of another legitimate user as a parameter.

Prevention

Ruby on Rails

User management and authentication are not native features of Rails, but they can easily be added by either writing the code or adding a gem, such as Devise.

Authorized user

A simple manual approach is to add a method that retrieves the current, logged-in user looking up active sessions to the Application Controller. It is frequent to add e.g. current_user method in the ApplicationController class to make sure every controller in the application inherits it.

class ApplicationController < ActionController::Base
  def current_user    
      User.find_by(id: session[:user_id])  
  end
end

Such method takes the user ID value from the user’s session and is resilient against tampering. It can be easily invoked by other controllers such as the one shown in this snippet:

def restricted
  @user = current_user
  if !(@user)
    flash[:error] = "Sorry, invalid user"
    redirect_to public_index_path
  end
end

Role-based authorization

It’s common to set access restrictions based on roles in order to check if a user has a specific role (such as administrator) and either allow access or redirect with an “Access Denied” message. Roles are attributes associated with a user account and implemented in a User schema and model.

The simplest scenario is a binary role scenario when a user can be either an administrator or an ordinary user. Add a boolean attribute to the User schema to indicate whether a user is an administrator or not and check the role checking the @user.admin attribute value:

def restricted
  @user = current_user
  if !(@user) || !(@user.admin)
    flash[:error] = "Sorry, the user is not an administrator"
    redirect_to public_index_path
  end
end

Controller filters

Role-base controls can be also enforced using “before” filters in the Controller classes in order to halt the request cycle if a condition is not met. In the following snippet, the restricted method can be invoked only by users who satisfy the administrative filter:

class AdminController < ApplicationController

  before_filter :administrative

  def restricted
    @user = current_user
    if !(@user) || !(@user.admin)
      flash[:error] = "Sorry, the user is not an administrator"
      redirect_to public_index_path
    end
  end

  private

  def administrative
    if !current_user.admin
     redirect_to public_index_path
   end
  end

end

Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application.

References

Rails Guides - Action Controller Overview Rails Guides - Securing Rails Applications RailsApps Project - Rails Authorization Devise