Caution! - Many of these posts are creepy-old in the Ruby on Rails world (before 1.0!)
The :author => Charles Abbott now blogs here

Active Directory Authentication with acts_as_authenticated

2006-08-17   [ 1 comments ]

Finally a chance to reference a ruby experience from the workplace! The last couple of day's I've been developing a rails based intranet webapplication that would require some form of authentication. Considering my application will be at work I thought it would be great to minimize the number of passwords my co-workers had to remember. What better way to do that than to authenticate via ActiveDirectory!

I had a lot to consider, so the first thing to do was determine what all the requirements were.

1) I need to have a User object for relationships within rails.

Page's belong to Users - so I can't just map everything to the ActiveDirectory db because then I can't build those relationships (since AD don't seem to have Primary Keys being an OO db and all.)

I'm also a fan of acts_as_authenticated plugin, so I would like to keep all of it's functionality. So where did I begin?

I had to decide how to handle users. Simple enough, first the user would log in. I would lookup his username in the Active Directory and create a new ActiveDirectory::User class from it (activedirectory library).

With that I will authenticate the User with the libraries authenticate() method. If it authenticated I would take the password I was given and extract the username, and email from the ActiveDirectory::User object and create a new User object from it.

Next I would use acts_as_authenticated's built in functions to save my new User object's password and other information into my local database. I will then authenticate locally using acts_as_authenticated 's built in methods, and pass the rest off to that plugin.

But why would I want to do all of this? Well it all simply stemmed from a few concerns I had about this setup:

What if the user attempts to login and Active Directory is down?

- well if I have already copied the user's data into the local database, then I'll simply authenticate locally instead.

What if the user changes his information in Active Directory?

- everytime the user successfully authenticates I will synch up the local database, to keep it current.

Will I use ActiveDirectory for Permissions, or will I handle those locally?

- After much thought I prefered to handle it locally. Mainly because I wouldn't want to keep hitting ActiveDirectory throughout the application's use. Plus it seems like a better idea to keep from losing relationships, or having to rely on ActiveDirectory for the application to function. This way AD could go away for ever and I could still log people in locally and handle everything from within the application. Active Directory authentication is just the prefered method, not the only method!

Without further ado - let me provide you the code that I used to make it all happen. First my User.rb Model:

# Authenticates a user by their login name and unencrypted
#password. Returns the user or nil.
 def self.authenticate(login,password)
  begin
  u = ActiveDirectory::User.find(login)
  rescue => er
  # User wasn't found so we'll attempt to authenticate locally
  logger.warn("Failed: AD Authentication for #{login} ERROR: #{er}")
  return self.authenticate_locally(login,password)
  end
  # Authenticate User
  begin
   u.authenticate(password)
   # Sync Active Directory User with Local User
  begin
    if self.sync_activedirectory_account(u,password)
    return self.authenticate_locally(login,password)
   else
    return nil
   end
  end
  rescue => er
   logger.warn("Authentication Failed \n ERROR: #{er}")
  end
end


def self.sync_activedirectory_account(active_directory_user,password)
 # Copy data over to the local database
 # if there is already a local user copy over the information
 u = active_directory_user
 if User.find_by_login(u.username)
   logger.info("Updating User Information")
   user = User.find_by_login(u.username)
   user.password = password
   user.password_confirmation = password
   user.email = u.email
   begin
    user.save!
    return true
   rescue => er
    logger.warn("ERROR: Attemping to Update Local User Information Failed \n #{er}")
   end
  else
   user = User.new(:login => u.username.to_s, :password => password.to_s, :password_confirmation => password.to_s, :email => u.email.to_s )
   begin
    user.save!
    logger.info("#{u.username} User Created")
    return true
  rescue => er
   logger.warn("Could not create new user #{u.username} - ERROR: #{er}")
    return false
   end
  end
end


def self.authenticate_locally(login, password)
 logger.info("Authenticating Locally...")
 u = find_by_login(login) # need to get the salt
 # modified for better logging
 if u && u.authenticated?(password)
  logger.info("Locally Authenticated")
   return u
  else
   return nil
  end
end

You will also need to add the following to your Environment.rb to make it all work:

ENVIROMENT.RB
--------------------
# This will have to placed in your /config/enviroment.rb file
# you may need to replace 'require' with require_gem if you installed activedirectory as a gem.
# I personally prefer to place the active_directory.rb and the /active_directory directory in my /lib folder
# to make sure that it doesn't go anywhere.
-------------------------------------------
require "active_directory"
ActiveDirectory::Base.server_settings = {
 :host => "ADSERVER.DOMAIN.COM",
 :username => "DOMAINADMINACCOUNT",
 :password => "PASS",
 :domain => "DOMAIN.COM",
 :base_dn => "DC=domain, DC=com"
}

:author => "Jim Fisher"
Ruby and Rails - Programming Euphoria
 


Charles said:

Kudos to my friend Jim for allowing me to edit and post his write up on the site!

Giving back to the community - always a good thing =)

2006-08-17 20:26:43 UTC

What?

Who?              Link?



Frameworks Good or Bad?   :date => "2007-10-06"
Where is ForTheCode.com Going?   :date => "2007-09-23"
Refactoring - Vital to Software Development   :date => "2007-09-23"
Mongrel Cluster a quick note - and extra notes   :date => "2007-05-20"
Linux Mongrel and Rails   :date => "2007-05-15"
form_remote_tag revisited   :date => "2007-01-07"
How To: Ubuntu 6.10 Edgy on Rails part 3   :date => "2006-12-30"
How To: Ubuntu 6.10 Edgy on Rails part 2   :date => "2006-12-24"
How To: Ubuntu 6.10 Edgy on Rails   :date => "2006-12-22"
verify ... 5.times do cycle   :date => "2006-09-25"
country_select, country_options_for_select, mail_to   :date => "2006-09-05"
Generate and Send Email in Rails   :date => "2006-08-26"
FDF Model, gsub, and send_data   :date => "2006-08-18"
Active Directory Authentication with acts_as_authenticated   :date => "2006-08-17"
Apache2 proxy with Lighttpd - FastCGI for Rails   :date => "2006-08-08"
reverse! && a simple file Upload Class   :date => "2006-07-29"
send_file - a link to download a file   :date => "2006-07-24"
Environments (production, development, test) and cache_pages   :date => "2006-07-04"
.class .methods .instance_variables   :date => "2006-06-14"
select_tag :multiple => true   :date => "2006-06-01"
FileUtils, action_controller rescues   :date => "2006-05-20"
file_field_tag, File.size, File.path, FileUtils.mv   :date => "2006-05-15"
javascript_include_tag, stylesheet_link_tag   :date => "2006-05-02"
submit_to_remote, form_remote_tag, script.aculo.us   :date => "2006-04-30"
periodically_call_remote, simple_format   :date => "2006-04-26"
observe_field - Ajax!   :date => "2006-04-21"
h method, TextHelper, sanitize(), strip_tags()   :date => "2006-04-15"
Rails API :My API [.count(), link_to, text_area :size]   :date => "2006-04-13"
Rails - HTML Select Tag   :date => "2006-04-05"
Pruning Old Sessions   :date => "2006-03-21"
If Elsif Else, and Searching Too!   :date => "2006-03-17"
SHA1 - A quick update   :date => "2006-03-15"
Initialized! good, Authorized? Great! part 2   :date => "2006-03-11"
Initialized! good, Authorized? Great!   :date => "2006-03-08"
Forms and Routing in RoR   :date => "2006-03-06"
My First RoR Post !   :date => "2006-03-05"