5 Minute Beta Authentication in Rails

Releasing your application in stages is often a very good idea, it allows you to build a core user base, and more importantly, it gives you time to winnow out the bugs before the full public launch. If you're inventive, you could even turn this process into a marketing coup by playing with scarcity (think gmail), but that's an area we won't get into here. Instead, we'll actually build a 'limited beta' subsystem in Rails.

Leveraging acts_as_authenticated

Instead of starting from scratch we'll use acts_as_authenticated as our starting point. If you haven't already, I encourage you to check it out, it's an excellent set of generators which will get you up and running in no time. Not to mention, the wiki itself is worth its weight in gold.

After getting through the basic setup of acts_as_authenticated we can move on to the next step. In order to have a true limited beta, we will put a login_required filter around our entire application with an exception of a few account management actions:

class ApplicationController < ActionController::Base
  session :session_key => '_app_session_id'

  include AuthenticatedSystem # generated by acts_as_authenticated
  before_filter :login_required, :except => [:welcome, :beta, :activate]

  # custom handler, will redirect to a 'welcome' splash page,
  # which does not require authentication.
  def access_denied
    respond_to do |accepts|
      accepts.html do
        store_location
        flash[:warning] = "Beta login required."
        redirect_to :controller => 'main', :action => 'welcome'
      end
        accepts.xml do
         headers["Status"]           = "Unauthorized"
         headers["WWW-Authenticate"] = %(Basic realm="Web Password")
         render :text => "Could't authenticate you", :status => '401 Unauthorized'
       end
     end
     false
  end
end

By introducing the filter in application.rb, our Rails application will force the user to validate their login credentials prior to execution of any action - hence, outsiders can't get in. Additionally, we also provided a custom access_denied method which will send our user to a nice splash page if the credentials are lacking.

Of course, now that we have locked everyone out, we need to let a few users in! And here is where all the magic happens: if you only want to grant full access to a select number of users, then you only have to guard the sign-up process. If a user is registered, then they must have had the right secret key, and we don't have to worry about anything else anymore. Alternatively, if you want to allow access to a subset of features, things get trickier - you might need another filter to check for access permissions on a more refined (action) level. However, we'll keep things simple and implement the former case.

Allowing beta sign-ups

Since we only care about guarding the sign-up process, we will require a special beta key as part of the registration process. By providing an extra text field in our form, we pipe the key into Rails and validate it in our sign-up procedure:

def signup
  # Verify beta key
  beta_verify = Digest::MD5.hexdigest("Secret application salt"+params[:user][:email])
  return redirect_to(:controller => 'main', :action => 'beta', :key => params[:key]) unless params[:key] == beta_verify

  # default acts_as_authenticated code follows ...
end

Here’s the trick, take the users email address, add a secret 'salt)' value, crank the results through a one-way cryptographic function (like MD5) and out comes a unique key which you can give to the user. Once the user fills in the form, we have enough data to recreate the key and validate it on the fly, and if everything is OK, then we process the sign-up!

Generating custom beta keys

Finally we can write a small command line ruby script to generate a custom key for a user. In this example, we'll use the email address and a custom salt value:

require 'rubygems'
require 'digest/md5'

key = Digest::MD5.hexdigest("Secret app salt:#{ARGV[0]}")
puts "http://www.newsite.com/beta/#{key}"

# usage:
# > ruby beta_key.rb user@email.com

Generate a few keys, send them to your eager users and let the beta begin!

Ilya GrigorikIlya Grigorik is a web ecosystem engineer, author of High Performance Browser Networking (O'Reilly), and Principal Engineer at Shopify — follow on Twitter.