Skip to main content

Mobomo webinars-now on demand! | learn more.

.ping_graph_img { -moz-box-shadow: 0px 2px 7px #aaa; border: 1px solid #fff; width: 600px; margin: 12px 0; }

I recently had the opportunity to investigate LDAP authentication and particularly SASL (Simple Authentication and Security Layer) binding with DIGEST-MD5 (“SASL/DIGEST-MD5”), because this approach provides network communication security with little sever configuration.

Currently, there are two popular LDAP gems ruby-ldap and ruby-net-ldap that provide API for simple binding on plain or SSL connections; however, in my test, both gems failed to create SASL/DIGEST-MD5 binding correctly.

Summarized below are some of my observations during the test:

  1. In my test with ruby-ldap, I tried several types of SASL binding mechanisms (i.e. DIGEST-MD5, GSS-SPNEGO, CRAM-MD5, etc.). No matter which type I chose, the SASL binding method always returned “Local Error” message. As we know, ruby-ldap is a wrapper for the libldap c library, which redirects all SASL calls to Cyrus SASL library. Since there is no easy way to debug the error, I used wireshark packet sniffer to examine the packets sequences, and found out that the SASL binding request is never sent out after the initial TCP syn/ack/syn-ack packets. It appears that the wrapper for the libldap c library is not working properly.

  2. A closer examination at the ruby-net-ldap reveals that the gem has implemented the full LDAP stack in Ruby even though it does not support SASL binding. The gem utilizes Ruby TCPSocket class to communicate with the LDAP server, and provides LDAP packet parser and constructor. ruby-net-ldap has recently been upgraded to net-ldap, which provides a framework for SASL binding.

Clearly, net-ldap is the only option for the SASL/DIGEST-MD5 implementation. To complete a SASL/DIGEST-MD5 authentication, the client needs to communicate with sever in the following sequence:

  1. The client sends an LDAP binding request with authentication=SASL and mechanism=DIGEST-MD5.
  2. The server generates an initial “digest challenge”, and sends it to the client.
  3. The client calculates “digest response” based on username, password, a random key and some information from the server’s challenge packet and returns the result to the server.
  4. The server validates the hash value and responses with a binding success packet.

The following net-ldap code ensures the correct SASL binding sequence and LDAP packet format:

  #msgid should always start with 0  #LDAP uses BER format to denote data, http://www.vijaymukhi.com/vmis/berldap.htm  #challenge_response is the Proc object which will parse the #digest challenge and provide the correct response to the server.    def bind_sasl auth    mech,cred,chall = auth[:mechanism],auth[:initial_credential],auth[:challenge_response]    raise LdapError.new( "invalid binding information" ) unless (mech && cred && chall)    n = 0    loop {       #start with initial credential for binding request       msgid = next_msgid.to_ber       #construct the LDAP payload from bottom up       sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)       request = [LdapVersion.to_ber, "".to_ber, sasl].to_ber_appsequence(0)       request_pkt = [msgid, request].to_ber_sequence       #send out the packet through tcp socket       @conn.write request_pkt       #read server challenge from the socket and parse out the LDAP payload       (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )       return pdu.result_code unless pdu.result_code == 14 # saslBindInProgress       raise LdapError.new("sasl challenge overflow") if ((n+= 1) > MaxSaslChallenges)       #decode the LDAP payload and pass it to challenlge_response proc       cred = chall.call( pdu.result_server_sasl_creds )       #this credential will be send to server as the challenge response     }  end  

In addition to implementing communication sequence, I forked a gem pyu-ruby-sasl from ruby-sasl to parse server challenge and generate the correct digest response and fixing several issues while connecting to Active Directory. The following example shows how to parse server challenge and generate client digest response.

  def sasl_digest_md5(bind_dn, password, host)        challenge_response = Proc.new do |cred|      pref = SASL::Preferences.new :digest_uri =>  "ldap/#{host}", :username => bind_dn, :has_password? => true, :password =>password         sasl = SASL.new("DIGEST-MD5", pref)         response = sasl.receive("challenge", cred)         response[1]      end      {:mechanism => “DIGEST-MD5”, :initial_credential => ‘’, :challenge_response => challenge_response}  end  

After I implemented the above methods, I only need following lines to perform a SASL/DIGEST-MD5 binding to Active Directory server:

  conn = Net::LDAP::Connection.new(:host => “pyub8bb.score.local”, :port => 389)  result = conn.bind_sasl(sasl_digest_md5(‘SCORE\pyu’, ‘password’, ‘pyub8bb.score.local’)) == 0  

Graph 1

image
*Graph 1 demonstrates the SASL biding packet sequence between client and server:

Graph 2

image

Graph 3

image
*Graph 2 shows the server digest-change packet format, and *Graph 3 shows the content.

Graph 4

image

Graph 5

image
*Graphs 4 and 5 show the client challenge response packet format and content.

In our latest OSS gem OmniAuth, we included several implementations for different LDAP authentication mechanisms in oa-enterprise sub-gem.

Thanks to all the good gems, implementing LDAP authentication is still fairly easy in Ruby. However, we should not forget about Java; its JNDI package provides all LDAP authentication mechanisms including much more complicated GSSAPI/Kerberos authentication. We should seriously consider using JRuby.

Categories
Author

The web application landscape has changed drastically in the past year or two. Where once every site was a silo unto itself and could reasonably expect users to create a unique login and password for each site, it is now a different story. I sigh every time I have to fill out yet another registration form, wishing instead for a simple "Connect with Facebook", "Sign in with Twitter", or "Log in with OpenID". At the same time, services are more interconnected than ever. One of the best ways to increase the popularity and viability of a new service is by piggybacking it onto the existing user bases of apps such as Twitter, Facebook, and Foursquare.

There are lots of authentication solutions out there for Rails. Many of them even have ways to connect to services such as Facebook or Twitter. But as I used these in project after project I noticed an emerging pattern: they all make too many assumptions about how I want to handle authentication in my application. Sure that makes it a quick start for the vanilla use case, but I honestly can't think of a time when I've dropped in an authentication solution and I was good to go. It's time for a change in perspective.

OmniAuth: The Unassuming Authentication Library

Today is the public release of OmniAuth. OmniAuth is a Rack-based authentication system that abstracts away the gritty, difficult parts of external authentication without assuming anything about what you actually want to do with that authentication information.

What does this mean for you? This means that you can make your app authenticate through Twitter, Facebook, LinkedIn, Google Apps, GitHub, Foursquare, and more and then have complete control from there.

Installation

OmniAuth is available as a gem:

gem install omniauth 

Diving In

Using OmniAuth is as simple as using any other Rack middleware. Of course, that's because OmniAuth is simply a Rack middleware. No complicated framework-specific configuration, just a collection of middleware to take the pain out of external authentication. Let's say I have a Sinatra app that I want to be able to authenticate via Twitter or Facebook. Here's what's required:

require 'omniauth' use Rack::Session::Cookie # OmniAuth requires sessions. use OmniAuth::Builder do   provider :twitter, "CONSUMER_KEY", "CONSUMER_SECRET"   provider :facebook, "APP_ID", "APP_SECRET" end

That's it! Now if I want to send my user to authenticate via Twitter, I send them to the URL /auth/twitter. For Facebook, /auth/facebook. The user will automatically be redirected to the appropriate site where they will be able to authenticate. Once authentication is complete, they will be redirected back to /auth/providername/callback and OmniAuth will automatically fill the omniauth.auth environment key with information about the user, so for my Sinatra client I just need to add:

get '/auth/:provider/callback' do   auth = request.env['omniauth.auth']   "Hello, #{auth['user_info']['name']}, you logged in via #{params['provider']}." end

Of course, I could do a lot more than just print out the user's name. I could also:

  • Check for an existing user via the uid key of the auth hash and log them in if one exists.
  • Create a user based on the uid and information from the user_info hash.
  • If a user is already logged in, associate this new account with the user so that they can log in using either service or post to both services using respective APIs.

The point here is that OmniAuth doesn't assume that you simply want to log a user in. It lets you make that judgment call and gives you all the information you need to do just about anything you need to do.

OmniAuth works right now for a wide variety of providers, and this list will continue to grow. OmniAuth today supports:

  • Facebook
  • Twitter
  • 37signals ID
  • Foursquare
  • LinkedIn
  • GitHub
  • OpenID (meaning Yahoo, Aol, Google, and many more)
  • Google Apps (via OpenID)
  • CAS (Central Authentication Service)
  • LDAP

A Breath of Fresh Auth

OmniAuth has been my worst-kept secret library for some time now. I've been using it as the go-to authentication strategy for new projects big and small for the last few months, and it feels really refreshing to have so much control over authentication without losing the drop-in ease of use. If you need external authentication but have found existing solutions to lack flexibility, please take a look!

OmniAuth is on GitHub with a growing set of documentation on the GitHub wiki and I have also set up a Google Group to handle any questions that might arise.

Categories
Author

More and more web applications are providing external logins through sites such as Twitter, Facebook, and more. It can be a bit of a pain to assemble suitable buttons for all of these services to display as the “NASCAR box” of logos for users to click when signing up or on to a site.

To make this a little bit easier, Intridea is releasing a collection of free and open-source logo buttons for use, well, however you’d like, but probably for these types of authentication scenarios. This collection is starting with twelve buttons:

The initial set created are for Twitter, Facebook, Myspace, OpenID, Google, Yahoo, Basecamp, Campfire, Present.ly, Aol, LinkedIn, and GitHub.

This collection will grow over time as we get suggestions (or forks) of new iconography. The idea is simply to provide a clean, consistent set of icons that can be used to represent some of the web’s most popular services. Each icon is available in 32×32, 64×64, 128×128, and 256×256 PNGs as well as an Illustrator CS4 source file that contains all of the buttons and individual CS3 .EPS files that contain each button individually.

To see a full download grid for each icon, visit the GitHub Project Page. If you have a logo you’d like to see included, please add a request for it to the GitHub Issues Page

Categories
Author
1
Subscribe to Authentication