Rails3, ldap and authlogic
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