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
current_ip_address = request.env['HTTP_X_REAL_IP'] || request.env['REMOTE_ADDR']
head :unauthorized if current_ip_address == "XX.XX.XX.XX"
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:
base.before_action :filter_ips_for_brand, :many, :other, :possible, :filters
# Protected methods
# For the current user, if his brand has a `whitelisted_ips` list, then verify the
# current connection is from a verified IP
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
verified = false
allowed_ips.each do |allowed_ip|
allowed_ip = IPAddr.new(allowed_ip)
verified = true
# Redirect back to main page if not verified
And then on each controller that needs to utilize those filters:
class MyController < ApplicationController
# Include the admin authorization "before_action" filters
Viola. Quick implementation for IP access restriction.