Simple Windows Active Directory LDAP Authentication with Rails

In this short tutorial I’ll describe an easy way to make your Rails application even more enterprise-ready ;-)

For our latest project, our customer asked to change the authentication module, they wanted to be able to use their Active Directory credentials to enter the application.

So, we have an existing rails application :

What do we need :

  • User should be able to keep using the old login system (at least temporarily, until all users are ‘upgraded’)
  • User should be able to login with his LDAP credentitals

I didn’t want to use a full-blown ActiveLdap implementation so I decided to customize the plugin generated code of the restful_authentication plugin. The application is still on Rails 1.2.3 and it uses an older version of restful_authentication. So if you’re going to try this yourself, there might be some differences with the code snippets below… but I’m sure you’ll figure it out :-).

Ok, let’s get started:

Since there are existing users in the system, I needed to connect their LDAP account to their application account.

Add ‘ldap_account’ field to the users-table

You can let the users fill this in (through ‘my profile’ page), let the admin do it, or do it manually yourself. Once it is filled in, the user should be able to use it to log into the application.


class AddLdapAccountToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :ldap_account, :string
  end

  def self.down
    remove_column :users, :ldap_account
  end
end

Install the LDAP libraries on your system

You’ll need the ruby-ldap libraries installed on your system, for linux you can find them here: http://sourceforge.net/projects/ruby-ldap/

You need to build this to use in a Windows environment. For your convenience we ‘ve packaged the linux and windows versions as a gem, get them here (but use at your own risk, we don’t provide support on this ;-) :

LDAP connection configuration

I found some useful information in the following article: http://blog.craz8.com/articles/2007/02/28/rails-and-ldap-gotchas/

And ofcourse in the ruby-ldap docs: http://ruby-ldap.sourceforge.net/rdoc/

Create a file ldap_conn.rb in the lib-folder :


require 'ldap'

def ldap_authenticated?(login,password)
  return false if password.blank?
  ldap_host = '10.10.10.10' #LDAP server IP
  ldap_port = 389

  begin
    ldap_conn = LDAP::Conn.new(ldap_host, ldap_port)
    ldap_conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
    ldap_conn.bind( login, password )
    true
  rescue
    return false
  end  
end

LDAP authentication methods

  • require ‘ldapp_conn’ in user.rb
  • add a method ‘ldap_autenticate’ in user.rb :

  # restful_authentication method
  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  def self.authenticate(login, password)
    u = find_by_login(login) # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end

  def self.ldap_authenticate(login, password)
    # Allow 'classic' login method
    if find_by_login(login)
      return User.authenticate(login, password)
    elsif u = find_by_ldap_account(login.upcase)
      return (ldap_authenticated?(login,password) ? u : nil)
    else
      return nil
    end
  end
  • call the new method from the account_controller login-action (or sessions_controller/create in the new plugin version)

  def login
    return unless request.post?
    self.current_user = User.ldap_authenticate(params[:login], params[:password])
    if logged_in?
      if params[:remember_me] == "1" 
        self.current_user.remember_me
        cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
        # store in cookie the module where the user logs in.
        cookies[:module] = {:value => params[:select][:module], :expires => self.current_user.remember_token_expires_at}
      end
      flash[:notice] = "Logged in successfully" 
    else
      flash[:error] = "Wrong login credentials !" 
    end
  end

Case sensitvity issue

I ran into a small problem regarding case sensitivity: the Active Directory authentication is not case sensitive, but the User.find_by_ldap_account(‘account’) is.

I solved this by making sure the ldap_account field in the users table was always saved as uppercase in the DB, and by doing the user lookup like this: find_by_ldap_account(login.upcase) (ldap_authenticate method)

Secure authentication (SSL)

It should be straightforward to do this LDAP authentication through SSL (so the login and password are not sent in clear text over the network). Instead of the method LDAP::Conn.new(...) you can use LDAP::SSLConn.new(...). However, I haven’t been able yet to get it to work, and I’m guessing it has something to do with the network settings/permissions.

Extra: Active Directory search

To execute a simple search you could do something like this:


    ldap_conn.bind( 'login', 'pass' ) do |conn|
      base = 'cn=yourname,OU=Users,OU=bla,OU=bla,dc=yourdomain,dc=com'
      results = conn.search2(base, LDAP::LDAP_SCOPE_SUBTREE, '(cn=name*)')
      results.each { |entry| puts entry['cn'] }
    end

The ‘cn=name*’ part is your search parameter.

You’ll need to customize the ‘base’-string according to your environment of course.

Entries per category

  1. docpublisher (5)
  2. ec2 (2)
  3. events (5)
  4. rails (6)
  5. ruby (12)