Down the rabbit hole: OAuth, service accounts and Google Apps

I have quite a few small programs which use Google's APIs for one thing or another:

  • Updating pages on Google Sites automatically
  • Reading a Google Docs spreadsheet and sending SMS reminders for a rota inside it
  • Reading a Google Calendar

Until recently, reading a public Google Calendar didn't require authentication - one could simply consume the XML from the calendar's URL and work with it using the GData API. Google knocked that on the head at some stage. Shortly afterwards, I woke up to a slew of error e-mails marking the apocalypse.

The apocalypse is how I refer to the day when Google finally removed the ability for scripts like mine to authenticate by presenting a username and password. For sure, hard-coding these into a script is not good practice, but it worked well for many years, and was a lot simpler and better documented than the alternative...


Much has been written elsewhere about OAuth, but the main problem I had was that Google's examples all seemed to centre around the idea of bouncing a user in their web browser to a prompt to authorize your use of their data. This is all very well for interactive web applications (and, indeed, much better than asking users to trust you with their password), but where does it leave my non-interactive scripts?

I eventually dug up the documentation on service accounts. The magic few lines of Python are these:

from oauth2client.client import SignedJwtAssertionCredentials
json_key = json.load(open('pkey.json'))
scope = ['']
credentials = SignedJwtAssertionCredentials(json_key['client_email'], json_key['private_key'], scope)

The JSON for the key can be obtained via the Google APIs developer console. Most Google APIs, and things built on top of them, can then take the credentials object, and authenticate as a service account. Apparently you can also do really clever things like impersonating arbitrary users in your Google Apps domain - for example, to send calendar invites as them - but that's for another day.