Key points

The story

I’ve been working on a client project consolidating most of their various internal databases, spreadsheets and ad-hoc lists into a coherent and centralized web application – employee lists, inventory and so on. Early on the client said to me: “Since we are using Google Apps for email, calendar, document sharing and pretty anything else it can do, it would be really nice if we could simply login to the application you are building using our Google Apps logins.” I said: “Sure, how hard can it be?” It was a fair request that made a lot of sense. After all, everybody hates another login to remember, so wouldn’t it be nice if the employees was simply – “as by magic” – instantly signed in to this new application?

I knew a lot of people was doing sign in with ordinary Google accounts. I had also implemented it myself for darebusters, although the easy way using the very nice RPX solution from JanRain. I could see that I could integrate with Google Apps using both OpenID and OAuth. I had some vague ideas about OAuth being “newer” and “cooler” than OpenID, and also I was using Warden and Devise for authentication, for which a nice OAuth extension existed, so I set out to authenticate against Google Apps with OAuth.

Big mistake: OAuth stands for Open Authorization, not Open Authentication. It says so, right there in the big green box. OAuth is not well suited for authentication, because the standard dictates that new security tokens are generated for every request. The OAuth extension for Warden comes with a nice example for Twitter and my guess is that this works fine. Twitter apparently doesn’t follow this aspect of the OAuth standard and allows the same security tokens to be used over and over again. This in turn makes it possible to save these tokens in a local database and use them for subsequent authentications without asking the user to sign in again. Not so for Google.

But that’s what I thought, and so I went ahead and implemented OAuth authentication using the extension for Warden. This was quite a pain to implement, as you mostly get HTTP errors without much detail back when you don’t do things exactly right. Remember that I didn’t know that OAuth is not meant for authentication and I was a bit confused as to how I went ahead and simply asked if the user was signed in. I ended up with a solution where I asked Google’s API for the user’s email address, since I in turn needed the email to identify the user in my own application. My working implementation was something on the lines of this, a mechanism that would in fact be very useful if I needed authorization to grab some data from the user’s Google account. If you want to go down this road, here is a few useful links.

I must admit I only realized my error when I tried out the implementation thinking as a user. I started asking my self why I had to choose a Google account and authorize the application to retrieve my email every single time I signed in. That was not how this fancy single sign-on thing was supposed to work. That’s when I took the time to read the aforementioned green box, and Google’s explanation of when to OAuth and when to use OpenID. So back to square one: I needed Google Apps authentication, and now I knew that OpenID was the way to go. From there it was it was easy to find the devise_openid_authenticatable gem which adds OpenID support to Devise in the same nice and modular fashion we have come to expect from this great authentication library. Everything is very nicely explained in the readme – that is, assuming you want to sign in using a “normal” OpenID service such as Google Accounts. To make it work with Google Apps, however, there are two minor snags to overcome – plus an annoyance that can be easily solved:

The OpenID sign in process starts by submitting an identity url to Devise as explained in the readme. For applications offering any OpenID account to authenticate, this identity is left to the user to enter. In this case, however, I wanted a big shiny “Google”-button that took the user directly to Google Apps authentication. Easily done by putting the identity_url in a hidden field, but what to use as an identity url (or end point, which was really the case here) for Google Apps – specifically, what to use to allow only login for the client’s specific Google Apps domain? A lot of searching – so much that unfortunately can’t point to the exact place I found this viable piece of information – gave me the solution: https://www.google.com/accounts/o8/site-xrds?hd=[myappsdomain.com].

Next problem: Ruby OpenID was acting all up, throwing errors and telling me that it couldn’t authenticate http://myappsdomain.com/openid?id=100664348167999321563. Huh, no wonder. There was no OpenID server running on my client’s domain. Google was doing that for us. Long story short, Ruby OpenID expects the user’s identity url and the end point for authenticating the user to match. I believe they do so on most OpenID services – it makes a lot of sense – but not so on Google’s. Luckily Google has created a gem that patches Ruby OpenID to handle this specific scenario: Apps Discovery. As soon as I installed this gem and required it in my application.rb (it was a Rails 3 app, it would be environment.rb in a Rails 2.x app) with require 'gapps_openid', things started working. Yay!

Since Devise requires an email to create a user, I couldn’t use the create_from_identity_url method suggested in the devise_openid_authenticatable readme for creating new users. Rather, I needed to initialize the user here, and then only create him or her later when I also had the email. This gist shows how I did it.

And the annoyance? Well, since Google’s OpenID server runs https, you get a lot of warnings from the OpenID library about missing a certificate for ensuring the security of the connection. One way to solve this it to copy the certificate bundle from Google’s gem into, say, /config/certs/ and then configure OpenID to use it: OpenID.fetcher.ca_file = "#{Rails.root}/config/certs/ca-bundle.crt".

Bookmark and Share