Bubble Foundry


OpenID and oAuth on App Engine

by Peter.

Building on my previous post, here are some things I learned today while working to get OpenID and oAuth playing nicely with Django on App Engine.

While App Engine has a very nice login system that hooks seamlessly into Google Accounts, gaining access to the user’s data via one of the Gdata feeds requires an additional authorization (you would use oAuth of course). However, Google has also introduced a federated login method whereby you can send the user to one screen where they both login and approve your access via oAuth to their data. So, I gave up development speed in favor of something that is hopefully simpler for my users. If you want to know more, Joseph Smarr of Plaxo has a good writeup of how the hybrid OpenID + oAuth combination came about.

After many frustrating attempts trying to get any of the many Django OpenID apps to work, all of which seemed to go haywire when touching App Engine (even if they claimed support), I found the google-app-engine-django-openid app. It’s quite simplistic – it doesn’t seem to connect to Django’s auth system at all – but, most importantly, it worked right out of the box on my App Engine install. Thanks, Wesley! Since I am requiring all users to login via a Google account, I simply skipped the first step in google-app-engine-django-openid’s login process, having the user enter their OpenID URL, and went to the next step, endpoint discovery. Beyond that all I had to do was add the additional oAuth parameters to my OpenID request and, on the return, upgrade the signed request token to an access token. gdata-python-client provides all the oAuth mechanisms you need as part of its authorization suite.

Of course, getting an access token was easier said than done, due the gdata library’s oAuth module being designed with the normal oAuth authorization flow in mind. To start using it much later in the oAuth process required me to initialize a bunch of things I wouldn’t have otherwise needed to handle. In the interest in saving people time, here’s the final code:

signed_request_token = gdata.auth.OAuthToken(key=args['openid.ext2.request_token'], secret="")
gdata_service = gdata.service.GDataService()
gdata.alt.appengine.run_on_appengine(gdata_service)
gdata_service.SetOAuthInputParameters(gdata.auth.OAuthSignatureMethod.HMAC_SHA1, settings.GOOGLE_CONSUMER_KEY, settings.GOOGLE_CONSUMER_SECRET)
access_token = gdata_service.UpgradeToOAuthAccessToken(signed_request_token)
person.accessToken = str(access_token)

It’s not a lot of code, really, but it took a lot of poking through the gdata code to figure it all out! Note that the access token returned by UpgradeToOAuthAccessToken() is not a string but can easily be converted to and from a string for storage.

When using parts of the gdata-python-client library, always make sure to set the http transport to App Engine’s urllib: gdata.service.http_request_handler = gdata.urlfetch. In general, if you’re getting any sort of weird error from something that uses a network connection, it’s probably because you’re not using Google’s URL fetch service.

In additional, one key thing I learned is that, while you don’t get a secret back with your signed request token, you must pass along an empty string for the secret when calling gdata.service.GDataService.UpgradeToOAuthAccessToken. None or False will cause errors in gdata’s oAuth library.

Finally, testing. Because Google requires your realm to match your domain for the oAuth request, I couldn’t test the authentication and authorization process on my local machine running the App Engine SDK behind a router. Instead, I just keep pushed changes to my live install on App Engine. I’m sure there’s a better solution, most likely running the SDK on a machine with a public address and getting an oAuth consumer key from Google tied to that address. Also, I didn’t use it but the oAuth Playground looks like a good place to test your oAuth interaction with Gdata services.