Stubbing out OAuth or OpenID

We have an application that uses OAuth to authenticate users as having valid google identities. It works, but there was a few conditionals in there so that in development mode it just skipped that step, which offends one of my co-workers. I wonder if it’ll offend me when I’ve got more experience in this game.

Goal: Put in place a little service, using something like sham_rack or WebMock to impersonate one or other of the services. This is a story of 2 failures and 1 success, more or less. I talk in some detail about what the process is when you use OpenID or OAuth, and it’s worth noting that this is detail I’ve gleaned from looking at the actual traffic, with occasional checks of the standards, to see how I could make one particular thing work in one particular case. If you want a general description of how things work, then run away, run away now.

At the moment, the application uses OAuth to get access to a user’s contact list (ie, address book). Then it reads the owner of the contact list, fetching all of the user’s gmail contacts in the process, and authenticates the user as that person. Awesome. The process has many steps:

  1. User -> Application: I want to login
  2. Application -> Google: I want some contacts
  3. Google -> Application: okay, have this crazy number
  4. Application -> User: Login over there. Use this crazy number
  5. User -> Google: I’ve got this crazy number. And a username. And a password.
  6. Google -> User: Cool cool. Go over here
  7. User -> Application: Hi. Do you love me?
  8. Application -> Google: Pssst, about that crazy number..
  9. Google -> Application: Yeah, yeah, it’s cool.
  10. Application -> Google: Gimme contacts, kthxbye.

Our goal is to meddle with the conversation between the application and google, such that the user goes from our “start logging in” page directly to our “login successful” page without seeing any googlestuff. The only way I could think of to do this is to change step (4), so that when the OAuth library thinks it’s sending the user to the “login over there” url, it’s actually sending them to the “login successful” page. Accordingly, I started looking at the response from Google to the application in step 3, thinking to mock our login-successful URL in there.

FAIL. The OAuth thing is for the application to know what google’s login page is, not for Google to tell the application where to go. So it can’t be done for OAuth just by messing with the wire traffic.

But that’s okay, because using OAuth for this is silly anyway! We want to send a user to google with a question of: “what’s this guys email address?”, and google to answer that without actually giving us any of the poor bastard’s details. And this, happily, is what OpenID is for.

Now, the OpenID way:

  1. User -> Application: I want to log in
  2. Application -> Google: Where you at?
  3. Google -> Application: I’m at here
  4. Application -> User: Login here. Give ’em these details. Make sure they give me your email address. Go back to the donespot when you’re done
  5. User -> Google: Hi. I’m me. Can you tell them my email address.
  6. Google -> User: Cool. You’re you. Go to the donespot, and tell it that your email address is foo@bar.com.
  7. User -> Application: Hello, donespot. Here’s a number. And my email address.
  8. Application -> Google: Hey, here’s this number. They say you authenticated them. And that they are foo@bar.com. And some other stuff.
  9. Google -> Application: Yeah, I said that

The notable difference between the two is that in OAuth, we throw an authentication token back and forth until google says that dude’s authenticated, then we pull information directly from the server to google. In OpenID, we throw an authentication token back and forth, but the information we want goes from google, to the client, then to us, and we then throw it to google with the token and ask for confirmation that the authentication token and the information actually line up.

How do we achieve our goal in this case? How about we change google’s response in step (3), so google claims to be at our login-successful page? Then we can forge the confirmation in step 9 to say it’s all good. The fail this time comes from the query strings which are used to keep this whole show on the road. There’s a query string that indicates “login successful, here’s the juice”, and a query string that indicates “I want to login, what happens now?”. The ruby-openid library manages those query strings. We can put a /sessions/create?openid.mode=id_res in as the page to log in to, but the library chops off the query string and sends the user to /sessions/create?openid.mode=checkid_setup. At which point the openid library rightly says that it’s not being asked to confirm a session’s creation, it’s being asked to start the process, and what the hell are you doing you crazy fool?

So, in both cases, the working of the library prevents the only ways I can think of to prevent the user from getting across to google. My man-in-the-middle attacks, stymied! That’s actually not such a bad thing, in retrospect.

What, then, are we to do? Looking at my code that used OpenID to talk to google, I realised that it the library had a very small surface area. Then I wrote the following file, which I saved to “lib/open_id.rb” (because this is rails-land, referring to OpenID will hit the missing constant stuff, constantize OpenID to open_id, then require the right file, all by magic).

require 'openid'
require 'openid/extensions/ax'
require 'gapps_openid'

class OpenID::Consumer
  def redirect_url(*args)
    "/session/create"
  end

  def complete(*args)
    OpenStruct.new(:status => OpenID::Consumer::SUCCESS)
  end
end

class OpenID::AX::FetchResponse
  def self.from_success_response(response)
    OpenStruct.new(:data => { "http://schema.openid.net/contact/email" => User.first.email })
  end
end

Now any time we talk to the library, expecting it to talk to google, it just answers back nice and fast with something that works. Hooray. Success! Achieved with only a slight modification of the original goal.

Now I have a way of stubbing the library out. All I need now is a way of conditionally including it for development and test environments in a way that won’t offend my co-worker…

One thought on “Stubbing out OAuth or OpenID

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s