Doing HTTP-Auth with Ruby on Rails.

The Ruby on Rails bunch seems Web coder centric – no surprise there. Seems Web people prefer form/session based authentication while Unix weenies prefer HTTP-Authentication. Ever tried to get past form based authentication in a script? It’s a pain.

There are some issues with HTTP-Authentication in most Web Application environments but usually you can hack it in.

For Ruby on rails you don’t even need to patch. Create a User Model with a username and a passwd row somehow like this:

require 'digest/sha1' 

class User < ActiveRecord::Base
  def passwd=(str)
    write_attribute("passwd", Digest::SHA1.hexdigest(str))
  end 

  def passwd
    "*****"
  end 

  def self.authenticate(username, passwd)
    find_first([ "username = ? AND passwd =?",
               username,
               Digest::SHA1.hexdigest(passwd) ])
  end
end

Then add this to your app/controllers/application.rb:

  def authorize(realm='Web Password', errormessage="Could't authenticate you")
    username, passwd = get_auth_data
    # check if authorized
    # try to get user
    if user = User.authenticate(username, passwd)
      # user exists and password is correct ... horray!
      if user.methods.include? 'lastlogin'
        # note last login
        @session['lastlogin'] = user.lastlogin
        user.last.login = Time.now
        user.save()
      @session["User.id"] = user.id
      end
    else
      # the user does not exist or the password was wrong
      @response.headers["Status"] = "Unauthorized"
      @response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
      render_text(errormessage, 401)
    end
  end 

  private
  def get_auth_data
    user, pass = '', ''
    # extract authorisation credentials
    if request.env.has_key? 'X-HTTP_AUTHORIZATION'
      # try to get it where mod_rewrite might have put it
      authdata = @request.env['X-HTTP_AUTHORIZATION'].to_s.split
    elsif request.env.has_key? 'HTTP_AUTHORIZATION'
      # this is the regular location
      authdata = @request.env['HTTP_AUTHORIZATION'].to_s.split
    end 

    # at the moment we only support basic authentication
    if authdata and authdata[0] == 'Basic'
      user, pass = Base64.decode64(authdata[1]).split(':')[0..1]
    end
    return [user, pass]
  end

Now you can add before_filter :authorize to all of your controllers which need protection.

Tested with Webrick and lighttpd/FastCGI

Leave a Reply