I have an openldap server running and wanted to use it for authentication on a rails 3 application.

Setup
In my Gemfile, I have the following:

gem 'rails', '3.0.1'
gem 'authlogic', :git => 'http://github.com/odorcicd/authlogic.git', :branch => 'rails3'
gem 'declarative_authorization'
gem 'net-ldap'
gem 'rmagick', :require => 'RMagick'
gem 'capistrano'

First step was to create the user table. I just used scaffolding for this. Basically following the general authlogic instructions.

Me:ldap-1$ rails generate scaffold User username:string firstname:string 
lastname:string persistence_token:string last_request_at:datetime role:string

Since I’m not storing the passwords here, I could leave off my usual crypted_password and salt columns.

Next I generated the authlogic stuff.

Me:ldap-1$ rails generate authlogic:session user_session
Me:ldap-1$ rails generate controller user_sessions new create destroy

Since I just wanted to see if this works, I’m just posting a login form (user_sessions#new) and then flashing ‘Successfully logged in’ if it worked and rendering the new page if it didn’t.

Keeping this as simple as I could, everything is in the user model.

class User < ActiveRecord::Base
 
  acts_as_authentic do |c|
    c.validate_password_field = false
    c.logged_in_timeout = 2.hours
  end    
  
  def valid_password?(password)
    ldap_settings = YAML.load_file("#{Rails.root.to_s}/config/ldap.yml")[Rails.env]
    ldap_settings[:host] = ldap_settings['host']
    ldap_settings[:port] = ldap_settings['port']
    ldap_settings[:encryption] = { :method => :simple_tls } if ldap_settings['ssl']
    ldap_settings[:auth] = 
      { :method => :simple, :
username => "uid=#{self.username},#{ldap_settings['base']}", 
:password => password}
    ldap = Net::LDAP.new(ldap_settings)
    ldap.bind
  end
end

The validate_password_field is set to false, meaning that authlogic is not going to figure if the password is good or not. With this set to false, it looks for a method called valid_password?. I had tried to use a verify_password_method :valid_ldap? in the user_session model, but it was always ignored. Since it asked for a valid_password? method, that’s what I created. The ldap.bind line returns true if the username/password was good and false if not. I didn’t worry about error messages or anything. I just wanted to see if it worked.

To try to keep the configuration out of the model, I put it in a YAML file, which is loaded in the first step. This is what ldap.yml looks like:

development:
    host: ldap.example.com
    port: 636
    base: ou=people,dc=example,dc=com
    ssl: true

Ok, the port must be 636 and ssl must be true. I could have left these out, but I figured that I might get other options working someday.

By and large, that was all I needed to do. Here’s the bit of my route file:

get "user_sessions/new"
  get "user_sessions/create"
  get "user_sessions/destroy"
  resources :users
  resources :user_sessions
  match 'login' => 'user_sessions#new'
  match 'logout' => 'user_sessions#destroy'

TROUBLESHOOTING
When I first started I had a number of problems, so what I wanted was to just make sure that I could bind with my ldap server with just a plain ruby program. This is as basic as I could make it. In fact, this doesn’t even have inputs. I hard-coded a username and password into it so I didn’t have to worry about stray carriage returns or anything else. Once I got this script working, it was much easier to get my app working as well.

Here, say the username is jsmith and the password is giraffe.

#!/usr/bin/ruby

require 'rubygems'
require 'net/ldap'

ldap = Net::LDAP.new :host => "ldap.example.com", 
  :port => 636, 
  :encryption => { :method => :simple_tls },
  :auth => {
    :method=>:simple,
    :username=>"uid=jsmith,ou=people,dc=example,dc=com",
    :password=>"giraffe"
  }

if ldap.bind
 print "OK\n"
else
 print "Not OK\n"
end