Upcoming and OnDemand Webinars View full list

Simple Subdomain Authentication In Ruby on Rails

Derek

Using a subdomain as an 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
  end
  1. 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
    # ...
  end

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:WINDOWSSYSTEM32DRIVERSetchosts

Add an entry for your application domain

  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.

9. Don’t forget…

  • 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/hosts before 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.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project