Last Updated: February 15, 2018
·
6.094K
· snird

Restrict IP access in rails

It's not uncommon to restrict access to some parts of your app by IP.
Recently I had to implement such restriction as a quick fix for an enterprise customer -
until a more permanent restriction of access using LDAP will be in place.

The easiest implementation, is just to check in a before_action filter the IP of the current user (as many SO answers suggest)

The StackOverflow answers code look roughly like this:

class ApplicationController < ActionController::Base
  before_action :filter_ip_address

  protected

  def filter_ip_address
    current_ip_address = request.env['HTTP_X_REAL_IP'] || request.env['REMOTE_ADDR']
    head :unauthorized if current_ip_address == "XX.XX.XX.XX"
  end
end

But this code have this caveats (some where more relevant for me):
- No support for IP ranges
- Filter is implemented in the controller, where I need it to be utilized across many controllers
- Allowed IP is hardcoded

So to address each point:
- Support for IP ranges: We can use the IPAddr built in library, create an instance with our CIDR format for IP address range, and use the include? method to determine if the current user ip is in the range.
- Filter need to be reused across multiple controllers: Here there are many different approaches. I could create a controller class implementing the filter in it, and make all the controllers in need of IP protection inherit from it. But in the "Composition over inheritance" war, I favor composition. So I created a module implementing the filter to be included in every controller.
- IPs are hardcoded: Out of the scope here, just a boring implementation of CRUD for each customer in our side.

So what was the code like in the end?

The module for the filters, to be included in each controller looked like this:

require 'ipaddr'

module AdminAuthorizationFilters

    def self.included(base)
        base.before_action :filter_ips_for_brand, :many, :other, :possible, :filters
    end

    # Protected methods
    # --------------------------------------------------------------------------------
    protected

    # For the current user, if his brand has a `whitelisted_ips` list, then verify the
    # current connection is from a verified IP
    def filter_ips_for_brand
        user_ip = IPAddr.new(request.remote_ip)
        # An array of IPs and IP ranges that should be allowed. Stored on the current user.
        allowed_ips = current_user.brand.whitelisted_ips
        # Validate IP only if allowed_ips array is set, otherwise there is no IP restriction
        if allowed_ips
            verified = false
            allowed_ips.each do |allowed_ip|
                allowed_ip = IPAddr.new(allowed_ip)
                if allowed_ip.include?(user_ip)
                    verified = true
                end
            end
            # Redirect back to main page if not verified
            unless verified
                redirect_to "/"
            end
        end
    end
end

And then on each controller that needs to utilize those filters:

class MyController < ApplicationController
    # Include the admin authorization "before_action" filters
    include AdminAuthorizationFilters

    ...
end

Viola. Quick implementation for IP access restriction.