Syndication IconNew article alerts are available via Atom. Hide this message

LDAP authentication in an Apache fronted Rails app

If you manage anything but the simplest of setups you've probably got an LDAP server setup providing directory services to your network. If you don't you should probably stop reading now ;)

Authenticate using LDAP

The first step to getting your Rails application authenticating using LDAP is to get Apache to authenticate all requests before they reach the application. This stuff is tricky and Apache already has a rather lovely module, mod_authnz_ldap, that does all the heavy lifting for us.

<VirtualHost 193.219.108.xxx:443>
  # I've used port 443 above because I'm dealing with passwords.
  # [...snip...]
  <Directory /var/www/foo.example.com/current/public>
    AuthType Basic
    AuthName "Foo Application Control Panel"
    AuthBasicAuthoritative off
    AuthBasicProvider ldap
    AuthLDAPUrl ldap://ldap.example.com/ou=people,dc=example,dc=com?userid?one
    Require valid-user
  </Directory>
  # [...snip...]
  # Your normal Rails HTTP configuration goes here
</VirtualHost>

Look up the user in Rails

Okay, so any request that hits your application is now authenticated against your LDAP directory. Next, tell Rails to look for the user. For authentication I wrote a rather funky (if I do say so myself) mixin, Xeriom::Acts::ProtectedSystem.

module Xeriom # :nodoc:
  module Acts # :nodoc:
    module ProtectedSystem # :nodoc:
      def self.included(base)
        base.send(:extend, ClassMethods)
      end

      module ClassMethods
        def acts_as_protected_system
          include InstanceMethods
          send(:before_filter, :ensure_user_is_logged_in)
          send(:helper_method, :current_user)
          send(:helper_method, :logged_in?)
        end
      end

      module InstanceMethods
        def ensure_user_is_logged_in
          if !logged_in?
            authenticate_user
          end
        end

        def logged_in?
          !current_user.blank?
        end

        def current_user
          @current_user ||= User.find_by_id(session[:user_id])
        end

        def current_user=(user)
          @current_user = user
          session[:user_id] = user.blank? ? nil : user.id
        end

        def authenticate_user
          authenticate_or_request_with_http_basic("Protected Area") do |username, password|
            # Lock your application servers down to listen to only
            # the web tier or this will kick your ass.
            send(:current_user=, User.find_by_username(username))
          end
        end
      end
    end
  end
end

ActionController::Base.send(:include, Xeriom::Acts::ProtectedSystem)

Like the code licence section in the sidebar says: this code is totally public domain, just don't sue me. To use it just drop the code in your lib/ directory and then call acts_as_protected_system in your ApplicationController.

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  protect_from_forgery # because CSRF sucks!
  acts_as_protected_system # lock the door
end

For bonus points...

If you found this article useful, give me some love over at Working With Rails.

About the boy

A picture of Craig in grayscale

Hi, I'm Craig and I'm a Ruby coder. I live, work and play in London. I like scaling applications and eating yoghurt. Sometimes I climb rocks. Most of the time I climb back down.

You can contact me by email, MSN or Jabber. My address on all of these is craig@xeriom.net.

Licence

The entire content of this blog is public domain. Use it however you fancy. You don't even need to attribute it to me, although it would be nice if you did. Just don't sue me and we'll all be happy.

Interesting Blogs

I Work With Rails

Recommend Me

My Travels

I go places. Do you go places too? Let's meet up!.