Simple Subdomain Authentication In Ruby on Rails
Using a subdomain as an account key (ie – highgroove.heartbeathq.com where “highgroove” is the account key) is a great way to personalize a web application. Rails has a nifty plugin written just for this, but the implementation information is a bit scattered. Here’s a step-by-step guide for implementing, testing, and simulating this powerful feature.
1. Take a look at the Account Location Plugin
Coming in at a concise 30 lines of code, it’s an easy read.
2. Customize the plugin for your needs
The plugin assumes we’re working with an Account model and that an instance variable named @account is the Account associated with the current subdomain. In my case, I wasn’t working with an Account class and I also didn’t want to assign the associated object to an instance variable of the same name (For example, in some cases @account could be assigned to a different account than the one associated with the subdomain).
I’m working with a Heartbeat Dashboard. Here’s my modified code. Instead of installing the plugin, I created a DashboardLocation module:
module DashboardLocation
def self.included(controller)
controller.helper_method(:dashboard_domain, :dashboard_subdomain,
:dashboard_host, :dashboard_url,
:current_dashboard, :current_subscription)
end
protected
def default_dashboard_subdomain
current_dashboard.subdomain if current_dashboard
end
def dashboard_url(dashboard_subdomain = default_dashboard_subdomain, use_ssl = request.ssl?)
(use_ssl ? "https://" : "http://") + dashboard_host(dashboard_subdomain)
end
def dashboard_host(dashboard_subdomain = default_dashboard_subdomain)
dashboard_host = ""
dashboard_host << dashboard_subdomain + "."
dashboard_host << dashboard_domain
end
def dashboard_domain
dashboard_domain = ""
dashboard_domain << request.subdomains[1..-1].join(".") + "." if request.subdomains.size > 1
dashboard_domain << request.domain + request.port_string
end
def dashboard_subdomain
request.subdomains.first
end
def current_dashboard
Dashboard.find(:first,
:conditions => ["subdomain = ? and subdomain IS NOT NULL",dashboard_subdomain])
end
def ensure_current_dashboard
return true if current_dashboard
flash[:warning] = "Please select a dashboard to login."
redirect_to(:controller => '/home') and return false
end
end
3. Install the plugin (or your own module)
If you don’t need to make any modifications, install the plugin straight-up:
ruby script/plugin install http://dev.rubyonrails.org/svn/rails/plugins/account_location/
If you need a customized module like me, put it in your lib folder (i.e. lib/dashboard_locaton.rb) and require it in your environment.rb file (require ‘dashboard_location’).
4. Add some Test Helpers
I added the methods below to TestHelper to make it easier to test our subdomain functionality. Remember I’m using current_dashboard instead of @account to represent the subdomain record.
class Test::Unit::TestCase
...
# Puts a dashboard into the subdomain
def dashboard_setup(dashboard = dashboards(:derek_dashboard) )
@request.host = "#{dashboard.subdomain}.local.host"
assert_equal dashboard, current_dashboard
end
def clear_dashboard
@request.host = "local.host"
assert_nil current_dashboard,
"There is a current dashboard when there shouldn't be: #{current_dashboard}"
end
def current_dashboard
Dashboard.find(:first, :conditions => ["subdomain = ? and subdomain IS NOT NULL",dashboard_subdomain])
end
def dashboard_subdomain
@request.subdomains.first
end
5. Add some Functional Tests
# failure - invalid subdomain
@request.host = "invalid.local.host"
get :index
assert_redirected_to :controller => 'home'
assert_not_nil flash[:warning]
# success
dashboard_setup
get :index
assert_response :success
assert_template "dashboard/index"
6. Implement the functionality in our controllers
class ApplicationController < ActionController::Base
include DashboardLocation
...
7. Verify the tests pass
8. Simulate the environment on your development machine.
Alter your “hosts” file to re-route domain lookups to your local machine (127.0.0.1).
On Mac OSX and Linux:
$ sudo vi /etc/hosts
On Windows:
C:\WINDOWS\SYSTEM32\DRIVERS\etc\hosts
Add an entry for your application domain (i.e. heartbeathq.com):
127.0.0.1 heartbeathq.com
Add entries for any subdomain you want to test:
127.0.0.1 highgroove.heartbeathq.com
127.0.0.1 rubyonrails.heartbeathq.com
Refresh and clear your cache to make sure lookups are re-routed:
On Mac OSX:
Issue the command in the terminal:
$ sudo lookupd -flushcache
On Linux:
Linux is pretty good about reading your /etc/hosts file, but to be on the safe side, restart ncsd if it’s running:
$ sudo /etc/init.d/ncsd restart
On Windows:
Issue the command at the command prompt:
C:\>ipconfig /flushdns
You’ll now be able to access your application through a pretty URL like: http://highgroove.heartbeat.com.
- If you’re like me, you may be dealing with 2 records – a record authenticated via the subdomain and a user authenticated and placed in the session. Don’t forget to ensure that the user in the session has access to the subdomain record.
- Remove the entries we added to
/etc/hostsbefore your site launches. - Setup your real DNS server with a CNAME or other wildcard entry and your webserver of choice with the same wildcard mapping for your application.
That’s it! It’s another “why I love Rails” moment – subdomain-as-account-key functionality in 30 lines of code tested and ready for production.
P.S. Heartbeat, our Ruby on Rails control panel built during RailsDay 2006, is getting ready to emerge with some very powerful new features. Videos to come late this week.

Using the subdomains as a key is definetely clever. It may not have a huge variety of applications because it is not needed all too often, but I can see the value in doing it as an alternate in some specialty cases. I’d be interested in hearing what applications you’d use it in. – ben @ http://rubyonrailsblog.com/