Ruby on Rails
HowtoAuthenticateWithHTTP (Version #55)

Authentication in general

There are a number of pages that cover the topic of authentication—see Authentication for an overview.

simple_http_auth plugin

If you’re looking for something simple and easy, Coda Hale has provided a plugin: simple_http_auth.

A simple walkthrough

Begin with an Account model. (You can call it User or Member or whatever – just update the controller code below to reflect this.)

This model should be created with (at minimum):


create_table :accounts do |t|
  t.column 'login', :string
  t.column 'password', :string
end

The code for this model should include:


require 'digest/sha1' 
class Account < ActiveRecord::Base
  def password=(str) 
    write_attribute("password", Digest::SHA1.hexdigest(str)) 
  end 

  def password 
    "********"  
  end 

  def self.authenticate(l, p) 
    find_by_login_and_password(l, Digest::SHA1.hexdigest(p))
  end 
end

Next, you should add these private methods to your ApplicationController:


  private
    def authorise
      # Log in using session data if it exists.
      if id = session["account_id"]
        @account = Account.find(id) and return true
      end

      # Otherwise log in from authentication data sent in request header.
      login, password = get_auth_data
      if login and password and @account = Account.authenticate(login, password)
        session["account_id"] = @account.id
        return true
      end

      # If no auth data, or wrong auth data, issue a challenge.
      response.headers["Status"] = "Unauthorized" 
      response.headers["WWW-Authenticate"] = 'Basic realm="Realm"'
      render(:text => "Authentication required", :status => 401)       
    end 

    def deauthorise
      session.delete("account_id")
    end

    def get_auth_data 
      auth_data = nil
      [
        'REDIRECT_REDIRECT_X_HTTP_AUTHORIZATION',
        'REDIRECT_X_HTTP_AUTHORIZATION',
        'X-HTTP_AUTHORIZATION', 
        'HTTP_AUTHORIZATION'
      ].each do |key|
        if request.env.has_key?(key)
          auth_data = request.env[key].to_s.split
          break
        end
      end

      if auth_data && auth_data[0] == 'Basic' 
        return Base64.decode64(auth_data[1]).split(':')[0..1] 
      end 
    end 

Finally, in any controller where you want to require HTTP Basic Authentication, use authorise as a before_filter:


before_filter :authorise

If you want to let people log out without closing their browser, create an action in a controller that calls the deauthorise method.

Apache/mod_fastcgi users

If you are using Apache and mod_fastcgi, this method will likely not work out of the box. Apache does not allow CGI scripts access to HTTP authorization headers by default. You will need to configure FastCGI to pass the Authorization header to your scripts . Short version: add -pass-header Authorization to the FastCgiServer startup directive, or FastCgiConfig -pass-header Authorization, for Apache 2, or -pass-header HTTP_AUTHORIZATION for Apache 1. For this, you will need access to apache’s config file.

If you cannot change the mod_fastcgi configuration, you may be able to use mod_rewrite to pass the Authorization header to Rails. Modify the RewriteRule that rewrites to the dispatcher to:


RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]

The Authorization header will now be available in request.env[‘X-HTTP_AUTHORIZATION’]. Depending on your mod_rewrite configuration, you may find the environment variable gets prefixed with REDIRECT_ one or more times.

Apache/mod_fcgid users

Apache2 does not pass HTTP authentication headers
to CGI/FCGID scripts, so the only way is to authenticate via Apache (htaccess) and check request.env[“REMOTE_USER”] for already validated user.

category: Howto

Authentication in general

There are a number of pages that cover the topic of authentication—see Authentication for an overview.

simple_http_auth plugin

If you’re looking for something simple and easy, Coda Hale has provided a plugin: simple_http_auth.

A simple walkthrough

Begin with an Account model. (You can call it User or Member or whatever – just update the controller code below to reflect this.)

This model should be created with (at minimum):


create_table :accounts do |t|
  t.column 'login', :string
  t.column 'password', :string
end

The code for this model should include:


require 'digest/sha1' 
class Account < ActiveRecord::Base
  def password=(str) 
    write_attribute("password", Digest::SHA1.hexdigest(str)) 
  end 

  def password 
    "********"  
  end 

  def self.authenticate(l, p) 
    find_by_login_and_password(l, Digest::SHA1.hexdigest(p))
  end 
end

Next, you should add these private methods to your ApplicationController:


  private
    def authorise
      # Log in using session data if it exists.
      if id = session["account_id"]
        @account = Account.find(id) and return true
      end

      # Otherwise log in from authentication data sent in request header.
      login, password = get_auth_data
      if login and password and @account = Account.authenticate(login, password)
        session["account_id"] = @account.id
        return true
      end

      # If no auth data, or wrong auth data, issue a challenge.
      response.headers["Status"] = "Unauthorized" 
      response.headers["WWW-Authenticate"] = 'Basic realm="Realm"'
      render(:text => "Authentication required", :status => 401)       
    end 

    def deauthorise
      session.delete("account_id")
    end

    def get_auth_data 
      auth_data = nil
      [
        'REDIRECT_REDIRECT_X_HTTP_AUTHORIZATION',
        'REDIRECT_X_HTTP_AUTHORIZATION',
        'X-HTTP_AUTHORIZATION', 
        'HTTP_AUTHORIZATION'
      ].each do |key|
        if request.env.has_key?(key)
          auth_data = request.env[key].to_s.split
          break
        end
      end

      if auth_data && auth_data[0] == 'Basic' 
        return Base64.decode64(auth_data[1]).split(':')[0..1] 
      end 
    end 

Finally, in any controller where you want to require HTTP Basic Authentication, use authorise as a before_filter:


before_filter :authorise

If you want to let people log out without closing their browser, create an action in a controller that calls the deauthorise method.

Apache/mod_fastcgi users

If you are using Apache and mod_fastcgi, this method will likely not work out of the box. Apache does not allow CGI scripts access to HTTP authorization headers by default. You will need to configure FastCGI to pass the Authorization header to your scripts . Short version: add -pass-header Authorization to the FastCgiServer startup directive, or FastCgiConfig -pass-header Authorization, for Apache 2, or -pass-header HTTP_AUTHORIZATION for Apache 1. For this, you will need access to apache’s config file.

If you cannot change the mod_fastcgi configuration, you may be able to use mod_rewrite to pass the Authorization header to Rails. Modify the RewriteRule that rewrites to the dispatcher to:


RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]

The Authorization header will now be available in request.env[‘X-HTTP_AUTHORIZATION’]. Depending on your mod_rewrite configuration, you may find the environment variable gets prefixed with REDIRECT_ one or more times.

Apache/mod_fcgid users

Apache2 does not pass HTTP authentication headers
to CGI/FCGID scripts, so the only way is to authenticate via Apache (htaccess) and check request.env[“REMOTE_USER”] for already validated user.

category: Howto