Google Apps OpenID with Rails and Devise

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".

8 thoughts on “Google Apps OpenID with Rails and Devise

  1. Pingback: Link dump for August 9th | The Queue Blog

  2. Hi Casper,

    Nice post! I did something similar but just used plain open id. Since all authentication goes thru google, why did you decide to include devise?

  3. Thanks. I use Devise because I still need to keep track of users locally, mostly for authorization purposes. Also, they a few “outside” users who are not on their Google Apps domain. They need a plain user login where Devise handles authentication.

    Oh, and Devise is so damn cool ;)

  4. Hi Casper – Have you tried to run this type of setup on Heroku at all? I seem to be running into problems using this setup, although I admittedly only use the openid_authenticatable, and trackable parts of devise (all my users are logging in thru google apps login). My logs don’t have anything that helps solve this and I was just wondering if you experienced something similar.

    Thanks!

  5. Sorry for the late comment, but for some reason I can’t think of an easy solution for a problem that may creep up.

    You mentioned storing that identity_url in a hidden field in the form so the customer doesn’t have to type it in (something I’m doing as well). How would one validate that the originally intended identity_url (or end-point, as it were) is being used (or, validate the returned identity_url from Google is of the proper domain)? If a malicious user wanted to, could they not use something like Firebug to change the hidden field to their own OpenID url, which would allow them to log in without a hitch if no other server side validation is being done.

    I’m still figuring out the internals of Devise, so I’m not entirely sure where I’d put this logic in the chain, though it may be something simple I’m overlooking.

    Thanks for the post. Exactly what I needed.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>