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.