Active directory authentication in rails with the restful authentication plugin
-Monday, September 22, 2008 By: Jon Kinney
The restful_authentication plugin by Rick Olson is THE defacto standard for authentication in rails apps. Nearly all mid-sized and small rails apps use it. It works right out of the box with MySQL, is REST compliant, and has some really nice code and test cases (specs now actually) to make sure everything is working. Rick does a good job of keeping it up to date as well and addresses security concerns of the community as they are brought up.
But what happens when you want to customize this plugin? I've made it work with rails version of "forms" authentication with a SQL 05 server, that wasn't too hard. I've changed it to have personal questions per user account and require correct answers to those questions before sending an email to reset a user's password. All of these kinds of modifications are expected. The plugins is just there to get you started. So what do we do when we cant to take Rick's code and authenticate against our internal user database stored in Active Directory? We extend his code yet again!
The hardest part of making this work was getting all the settings for authenticating to your specific LDAP server figured out. I suggest working with someone at your organization that has first hand experience with your company's Active Directory setup and specifically how the LDAP is configured in your environment.
Below is the user.rb for a rails system that needs to authenticate to Active Directory. You need LDAP services turned on (usually this is the case by default). You will need to check with your AD administrator anyway to get all the connection information, DSN, CN, etc.
To get started you will need to install the 'net-ldap' gem. At your command prompt type:
1 sudo gem install net-ldap
See the documentation here: http://github.com/roryo/ruby-net-ldap/. This code is a drop in replacement for the self.authenticate method in the restful_authentication plugin. After adding the line: require 'net/ldap' at the top, the only other change is to remove the existing self.authenticate method and replace it with the two shown in this post. Optionally, you can remove all the password code that is no longer in use. The exceptions are the "self.encrypt" method and the "encrypt" method which are still in use by the "remember_me_until" method.
1 #place at the top of the user.rb model
2 require 'net/ldap'
1 #LDAP AD Authentication (replaces self.authenticate for the restful_auth plugin)
2
3 def self.authenticate(username,password)
4 #passing in the user with the dc attached... you should also be able to use the full CN
5
6 ldap_con = initialize_ldap_con(username + "@domain.com", password)
7 treebase = "DC=domain,DC=com"
8 user_filter = Net::LDAP::Filter.eq( "sAMAccountName", username )
9 #op_filter = Net::LDAP::Filter.eq( "objectClass", "organizationalPerson" ) #not used
10
11 #ldap will automatically bind when trying to preform a search or modification, so we don't call .bind here.
12 #.bind is only if you want to just return true or false, but we want to look up some attributes!
13
14 if ldap_con.search(:base => treebase, :filter => user_filter,
15 :attributes => ['dn','sAMAccountName','displayname','SN','givenName']) do |ad_user|
16 #try to find the user locally and if they aren't there then create them.
17
18 local_user = find_by_login(ad_user.samaccountname.to_s)
19 if !local_user
20 local_user = User.new(:login => ad_user.samaccountname.to_s, :name => ad_user.displayname.to_s, :email => ad_user.givenname.to_s + '.' + ad_user.sn.to_s + '@domain.com')
21 local_user.save false #pass false to skip validations
22 return local_user
23 else
24 #They were found in the local database but to keep the local user info sync'd
25 #each time we login we update the local db's name and email fields.
26 local_user.update_attributes(:name => ad_user.displayname.to_s, :email => ad_user.givenname.to_s + '.' + ad_user.sn.to_s + '@domain.com')
27 local_user.save false
28 return local_user
29 end
30 else
31 return nil #they didn't authenticate to AD
32 end
33 end
34
35 #helper method called from above
36 def self.initialize_ldap_con(user_name, password)
37 ldap = Net::LDAP.new
38 ldap.host = "your_ldap_server_IP"
39 ldap.port = 636 #required for SSL connections, 389 is the default plain text port
40 ldap.encryption :simple_tls #also required to tell Net:LDAP that we want SSL
41 ldap.auth "#{user_name}","#{password}"
42 ldap #explicitly return the ldap connection object
43 end
News & Events
Tech Review: Web Design For Developers - June 2009
A friend of mine in the web development community is releasing a book called "Web Design for Developers: A Programmer's Guide to Design Tools and Techniques". I was asked to do a tech review of the book and will be sharing my thoughts as well as some cool info presented in the book in a short series of upcoming blog posts. If you want to grab a beta copy of the book head over to my favorite publisher The Pragmatic Programmers.
First Class Audio Production - March 1st 2009
My most recent studio project was mixing and producing the latest a cappella album to come out of Eau Claire, WI. Until Proven Guilty is the Innocent Men's 4th studio album and marks a huge leap forward in recording and production quality for the group. Check back for demos of the mastered songs very soon! http://theinnocentmen.com.
Rate This Post: 0.0 (average)


