Routes Constraint
In this tutorial, we’ll create two different domains for our application. One is for our clients, and the other is for the administrators. Additionally, we will also restrict access to our admin users lists to administrators only.
Table Of Contents
What is Domain?
A domain name is the address of your website. This is what users put into their browser’s search box to get to your website. What we are using right now as our domain/hostname is localhost
with port 3000.
Host Authorization
Rails have a middleware called Host Authorization that prevents against DNS rebinding attacks. By default this feature only allows requests from 0.0.0.0
, ::
, and localhost
.
Now, in order to allow our hostname to be accessed, we have to whitelist the development hostname to our development config.
# config/environments/development.rb
require "active_support/core_ext/integer/time"
Rails.application.configure do
# ...
+ config.hosts << "client.com"
+ config.hosts << "admin.com"
end
Read Carefully:
Clearing the entire whitelist with
config.hosts.clear
will also do the trick. This will let through requests for all hostnames.
Next step is configuring your hosts file. Hosts files are used to test the DNS system by redirecting a Web browser or other application to a specified IP address.
In your terminal go to /private/etc
, open the hosts
file using editor.
$~> cd /private/etc
$~/private/etc> sudo {editor} hosts
Then add the following:
`127.0.0.1 client.com`
`127.0.0.1 admin.com`
We can now access our application with client.com:3000
or admin.com:3000
. All that’s left is restricting the access of the admin users lists to admins only.
# app/controllers/admin/users_controller.rb
class Admin::UsersController < ApplicationController
+ before_action :authenticate_user!
+ before_action :check_admin
+
def index
@users = User.page params[:page]
end
+
+ def check_admin
+ raise ActionController::RoutingError.new('Not Found') unless current_user.admin?
+ end
end
We can know who is accessing the admin users page by their genre, to do that, we have to make sure that a user is currently signed-in before accessing the page. When signed-in user is not an admin, it will raise a custom RoutingError with message Not Found
.
Route Constraint
Security check features are also included in the Rails Route, and this is the constraint. Routing constraints are checks that are built around routes to clean and validate information before it reaches a controller action.
Let’s try using a constraint.
# config/routes.rb
Rails.application.routes.draw do
devise_for :users
root 'welcome#index'
- resources :posts do
- resources :comments, except: :show
- end
+ constraints(ip: /127\.0\.0\.1$/) do
+ resources :posts do
+ resources :comments, except: :show
+ end
+ end
# ...
end
This will restrict all IP’s to access posts
and posts/comments
route except the ip 127.0.0.1
. Try accessing the posts page, it will show a RoutingError no route match.
We can also make a constraint module to make our routes clean. Create AdminDomainConstraint
and ClientDomainConstraint
under app/constraint
.
# app/constraint/admin_domain_constraint.rb
class AdminDomainConstraint
def matches?(request)
domains = ['admin.com']
domains.include?(request.domain.downcase)
end
end
# app/constraint/client_domain_constraint.rb
class ClientDomainConstraint
def matches?(request)
domains = ['client.com']
domains.include?(request.domain.downcase)
end
end
This will check if the request domain is included on the domain whitelist. Now let’s implement this constraints to our route.
# config/routes.rb
Rails.application.routes.draw do
devise_for :users
root 'welcome#index'
- constraints(ip: /127\.0\.0\.1$/) do
+ constraints(ClientDomainConstraint.new) do
resources :posts do
resources :comments, except: :show
end
end
resources :categories
- namespace :admin do
- resources :users
- end
+ constraints(AdminDomainConstraint.new) do
+ namespace :admin do
+ resources :users
+ end
+ end
# ...
end
Domain Config
Making a hardcoded domain whitelist is not really safe, in order to address this issue, let’s a config yml that will contain our domain whitelist. One yml
file that will contain the example of our whitelist, and one that we will actually use for our application.
# config/domain.example.yml
development:
admin:
- aaa.com
client:
- bbb.com
# config/domain.yml
development:
admin:
- admin.com
client:
- client.com
To exclude the domain.yml
file in commit, add it to .gitignore
.
# .gitignore
# ...
/node_modules
+
+ /config/domain.yml
Final step is to implement the config to client and domain constraint.
# app/constraint/admin_domain_constraint.rb
class AdminDomainConstraint
def matches?(request)
- domains = ['admin.com']
+ domains = Rails.application.config_for(:domain)[:admin]
domains.include? request.domain.downcase
end
end
# app/constraint/client_domain_constraint.rb
class ClientDomainConstraint
def matches?(request)
- domains = ['client.com']
+ domains = Rails.application.config_for(:domain)[:client]
domains.include? request.domain.downcase
end
end
That is all for Routes Constraint.