When making one of your apps available on the google apps marketplace and you're not aiming for the full integration, pretty much all you have to (or at least should) do, is to allow a user to sign-in to your app using his google account. This sounds easier than it actually is, because Google's OpenID - called Google Federated Login - is not really standard OpenID. Otherwise, using one of the existing plugins for authlogic (our authentication framework of choice) would have done the trick.

Our way to glory

We had a look at a couple possible solutions, including writing our own authentication library, or using a third-party solution. But that was either to complex, or it didn't exactly provide what we needed. So we ended up changing an existing plugins and our good old friend Dr Monkey Patch.

Authlogic trickery

The first thing to notice is, that if you use a fairly recent version of the open_id_authentication plugin (which is used by the authlogic-openid plugin and itself uses rack-openid and ruby-openid), binary logic's version ain't working - but this fork is! And, authlogic's auto_register feature didn't work for us at all. In theory, this should create a user-record whenever a non-existing person authenticates, but in our case, it led to infinite loops, because the user validated the session, which then tried to create a user, which validated the session... so we ended up writing our own auto-register method (more about that later).

Another issue was, that Google Federated Login uses Attribute Exchange (AX) with OpenID instead of the more common Simple Registration Extension (SReg). By specifying required fields in the acts_as_authentic definition within your user model, you can force the use of AX attributes in the request:


  acts_as_authentic do |c|
    c.logged_in_timeout = 1.hour
    c.openid_required_fields = [
      'http://axschema.org/contact/email',    # Email
      'http://axschema.org/namePerson/first', # First name
      'http://axschema.org/namePerson/last'   # Last name
    ]
  end

This makes Google happy, but in order to be able to extract the users email and name from the response, we had to patch the open_id_authentication plugin (our fork). By default the plugin parses the response as SReg, but we needed AX, so the only thing we had to change was


  OpenID::SReg::Response.from_success_response(response)

to


  OpenID::AX::FetchResponse.from_success_response(response)

These two rather small changes will allow your users to authenticate through Google.

Auto-Register new users

As mentioned earlier, authlogics built-in auto-register function caused some headaches. But we needed that feature for a seamless user experience, so we had to implement it ourselves. The easy part was to write the code that creates a user:


  class User
    def self.register_with_openid(openid_identifier, openid_ax_response)
      login = openid_ax_response['http://axschema.org/contact/email'].to_s

      user = User.find_by_login(login)
      user ||= User.new(:login => login)

      user.openid_identifier = openid_identifier
      user.name = [
        openid_ax_response['http://axschema.org/namePerson/first'],
        openid_ax_response['http://axschema.org/namePerson/last']
      ].select(&:present?).join(' ')

      user.save_without_session_maintenance # otherwise we cause havok!
    end
  end

Note the last line, which makes sure that we don't care about an associated session - would we just call save we would get an infinite loop as well! After taking care of that, we can go ahead and monkey-patch the open_id_authentication plugin to register a user after successful authentication:

  def complete_open_id_authentication_with_registration(&block)
    complete_open_id_authentication_without_registration do |result, openid_identifier, registration|
      User.register_with_openid(openid_identifier, registration) unless result.unsuccessful?
      yield result, openid_identifier, registration
    end
  end
  alias_method_chain :complete_open_id_authentication, :registration