Saturday, July 17, 2010

Android, OAuth, and Google App Engine

The single biggest delay in android2cloud I ran into was trying to get Android and App Engine to play nice over OAuth.

Getting Started

I ran through several different iterations of the account support in android2cloud before I finally managed to make this work. Save yourself the trouble, Focus on one thing at a time. This project can be broken down into component parts, and those are much easier to tackle. Half the time, I wasn't entirely sure whether Android, Signpost (the OAuth library for Android I was using), or App Engine's OAuth implementation was at the root of my problems, and debugging was frustrating when I tried doing it all at once. Save yourself that headache. Build in Java first. The first thing I did was build a working Signpost client that ran from the command line. I abstracted every single step of the OAuth protocol into its own method, and tied them all together in main(). When I got that authenticating against App Engine, I knew any subsequent OAuth issues were solely due to my handling of the protocol in Android.

Gotchas:

  • Signpost on Android doesn't work with the DefaultOAuth* classes. Use CommonsHttpOAuth* instead.
  • You need to make sure you add a header to your request, or App Engine will complain. Here it is:
    request.addHeader("Content-Type", "application/x-www-form-urlencoded");
  • Your consumer key and secret are both "anonymous"

Make Mine Mobile

Once you have the working Java implementation, it's time to port it over into Android. This is tricky, because you need to pass a lot of information between Activities, and sometimes you don't even control the activities, so managing your information gets to be a tricky business. Intent.putExtra will become your best friend, and a solid understanding of startActivityForResult and Intent.getIntent will save you a headache or four.

Advice:

  • Figure out how you're storing information ahead of time. It will save you trouble. Remember you need to store, at the very least, their token and secret.
  • Store your consumer and provider in fields. It's much easier than instantiating new ones every method.
  • I shouldn't even need to say this, but store your host, URLs, consumer key, consumer secret, and any other constants as... well... constants.
  • Add "btmpl=mobile" to your authorize URL to get a mobile version of the authorization page.

Another interesting note is the debate between WebViews and the browser. I chose to open a WebView for users to authorize the application with. As Abraham pointed out, that's asking a user to trust my application not to harvest their details through that WebView. He suggests I use an Intent and protocol filter to process the callback. I actually experimented with that method, and found it not to my liking, as it made the back button's functionality weird, and sent unexpected results to the application, at times. After his suggestion, though, I'm back to tinkering with it. If I can make it consistent and user friendly.

For those interested in how I handled the callback, I actually set a callback URL on the host. When the WebView loads a page, it checks the URL. If the URL matches the callback, it extracts the returned parameters. Otherwise, it just loads the page up. I found this to be the most immersive, user-friendly experience, so that's what I went with. I'll write again if I switch to the protocol method.

And, of course, all of my code for my OAuth implementation is in the project's source on Google Code. I'd be flattered if anyone actually used it, so hack it apart, take a look at how I did it, or anything else you want to do that will save you some time. And if you have suggestions or find bugs, go ahead and file an issue. I'll work on it.

No comments:

Post a Comment