My Django powered website allows users to submit events for a site calendar that is built upon Google Calendar. After an admin approves events, I use Google's Python Client Library to add, delete, or update events on the Google calendar associated with my personal Google account. I wrote this application a few years ago, and it used the ClientLogin method for authentication. I recently decided to upgrade this to the OAuth authentication method. The ClientLogin method isn't very secure and it doesn't play well with Google's two-step verification. After hearing about a friend who had his GMail account compromised and all his email deleted I decided it was long past due to get two-step verification on my account. But first I needed to upgrade my web application to OAuth.

In this post I'll boil down the code I used to implement the elaborate OAuth dance. It really isn't that much code, but the Google documentation is somewhat confusing and scattered across a bewildering number of documents. I found at least one error in the documentation that I will point out. Although I am using Django, I will omit details specific to Django where I can.

In addition to switching from ClientLogin to OAuth, I also upgraded to version 2.0 of the Google Data API. This had more implications for my calendar-specific code, and perhaps I can go over that in a future post.

Getting started and registering with Google

To understand the basics of OAuth, I suggest you read OAuth 1.0 for Web Applications. I decided to go for maximum security and use RSA-SHA1 signing on all my requests to Google. This requires that I verify my domain and then register my application with Google, which includes uploading a security certificate. Google provides documentation that describes how you can create a self-signing private key and certificate using OpenSSL.

Fetching a Request Token and authorizing access

To perform the first part of the OAuth dance, you must ask Google for a request token. When you make this request, you state the "scope" of your future work by listing the Google resources you are going to access. In our case, this is the calendar resources. You also provide a "consumer key" that Google assigned to you when you registered your application. This allows Google to retrieve the security certificate you previously uploaded when you registered. This is very important because this request is going to be signed with your private key. Fortunately the Python library takes care of all the signing details, you simply must provide your private key in PEM format. And finally, you provide a "callback URL" that Google will send your browser to after you (or your users) have manually authorized this request.

Once you have received the request token from Google, you have to squirrel it away somewhere, then redirect your (or your user's) browser to a Google authorization page. Once the user has authorized your application, Google sends the browser to the callback URL to continue the process. Here I show the distilled code I used that asks for a request token, then sends the user to the authorization page.

import gdata.gauth
from gdata.calendar_resource.client import CalendarResourceClient

USER_AGENT = 'mydomain-myapp-v1' # my made up user agent string

client = CalendarResourceClient(None, source=USER_AGENT)

# obtain my private key that I saved previously on the filesystem:
with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f:
    rsa_key = f.read()

# Ask for a request token:
# scopes - a list of scope strings that the request token is for. See
# http://code.google.com/apis/gdata/faq.html#AuthScopes
# callback_url - URL to send the user after authorizing our app

scopes = ['https://www.google.com/calendar/feeds/']
callback_url = 'http://example.com/some/url/to/callback'

request_token = client.GetOAuthToken(
        scopes,
        callback_url,
        settings.GOOGLE_OAUTH_CONSUMER_KEY, # from the registration process
        rsa_private_key=rsa_key)

# Before redirecting, save the request token somewhere; here I place it in
# the session (this line is Django specific):
request.session[REQ_TOKEN_SESSION_KEY] = request_token

# Generate the authorization URL.
# Despite the documentation, don't do this:
#    auth_url = request_token.generate_authorization_url(domain=None)
# Do this instead if you are not using a Google Apps domain:
auth_url = request_token.generate_authorization_url()

# Now redirect the user somehow to the auth_url; here is how you might do
# it in Django:
return HttpResponseRedirect(auth_url)

A couple of notes on the above:

  • You don't have to use CalendarResourceClient, it just made the most sense for me since I am doing calendar stuff later on. Any class that inherits from gdata.client.GDClient will work. You might be able to use that class directly. Google uses gdata.docs.client.DocsClient in their examples.
  • I chose to store my private key in a file rather than the database. If you do so, it's probably a good idea to make the file readable only to the user your webserver runs your application as.
  • After getting the request token you must save it somehow. You can save it in the session, the database, or perhaps a file. Since this is only temporary, I chose to save it in the session. The code I have here is Django specific.
  • When generating the authorization URL, don't pass in domain=None if you aren't using a Google Apps domain like the documentation states. This appears to be an error in the documentation. Just omit it and let it use the default value of "default" (see the source code).
  • After using the request token to generate the authorization URL, redirect the browser to it.

Extracting and upgrading to an Access Token

The user will then be taken to a Google authorization page. The page will show the user what parts of their Google account your application is trying to access using the information you provided in the scopes parameter. If the user accepts, Google will then redirect the browser to your callback URL where we can complete the process.

The code running at our callback URL must retrieve the request token that we saved earlier, and combine that with certain GET parameters Google attached to our callback URL. This is all done for us by the Python library. We then send this new token back to Google to upgrade it to an actual access token. If this succeeds, we can then save this new access token in our database for use in subsequent Google API operations. The access token is a Python object, so you can serialize it use the pickle module, or use routines provided by Google (shown below).

# Code running at our callback URL:
# Retrieve the request token we saved earlier in our session
saved_token = request.session[REQ_TOKEN_SESSION_KEY]

# Authorize it by combining it with GET parameters received from Google
request_token = gdata.gauth.AuthorizeRequestToken(saved_token,
                    request.build_absolute_uri())

# Upgrade it to an access token
client = CalendarResourceClient(None, source=USER_AGENT)
access_token = client.GetAccessToken(request_token)

# Now save access_token somewhere, e.g. a database. So first serialize it:
access_token_str =  gdata.gauth.TokenToBlob(access_token)

# Save to database (details omitted)

Some notes on the above code:

  • Once called back, our code must retrieve the request token we saved in our session. The code shown is specific to Django.
  • We then combine this saved request token with certain GET parameters that Google added to our callback URL. The AuthorizeRequestToken function takes care of those details for us. The second argument to that function requires the full URL including GET parameters as a string. Here I populate that argument by using a Django-specific method of retrieving that information.
  • Finally, you upgrade your token to an access token by making one last call to Google. You should now save a serialized version of this access token in your database for future use.

Using your shiny new Access Token

Once you have saved your access token, you won't have to do this crazy dance again until the token either expires, or the user revokes your application's access to the Google account. To use it in a calendar operation, for example, you simply retrieve it from your database, deserialize it, and then use it to create a CalendarClient.

from gdata.calendar.client import CalendarClient

# retrieve access token from the database:
access_token_str = ...
access_token = gdata.gauth.TokenFromBlob(access_token_str)

client = CalendarClient(source=USER_AGENT, auth_token=access_token)

# now use client to make calendar operations...

Conclusion

The main reason I wrote this blog post is I wanted to show a concrete example of using RSA-SHA1 and version 2.0 of the Google API together. All of the information I have presented is in the Google documentation, but it is spread across several documents and jumbled up with example code for version 1.0 and HMAC-SHA1. Do not be afraid to look at the source code for the Python client library. Despite Google's strange habit of ignoring PEP-8 and using LongJavaLikeMethodNames, the code is logical and easy to read. Their library is built up in layers, and you may have to dip down a few levels to find out what is going on, but it is fairly straightforward to read if you combine it with their online documentation.

I hope someone finds this useful. Your feedback is welcome.


Comments

comments powered by Disqus