My favorites | English | Sign in

Google App Engine

Retrieving Authenticated Google Data Feeds with Google App Engine (Python)

Jeff Scudder
April 2008
, updated April 2011 by Ikai Lan & Johan Euphrosine for OAuth.

I'm sure your mind is positively buzzing with ideas for how to use Google App Engine, and a few of you might be interested in building an app that interacts with some of Google's other services via our Google Data APIs. A number of Google's products expose a Google Data API, including YouTube, Google Calendar, and Blogger – a complete list is available here – and these APIs can be used to read and edit the user-specific data they expose.

In this article we use the Google Documents List Data API to walk through the process of requesting access from and retrieving data for a particular user. We use Google App Engine's webapp framework to generate the application pages, as well as the Google Accounts service to authenticate users with Google Accounts.

Introducing OAuth

Some Google Data services require authorization from your users to read data. All Google Data services require their authorization before your app can write to these services on the user's behalf. There are three methods in which a user can grant access to your application to access services on their behalf:

  • OAuth – The preferred, recommended way to make calls on behalf of users. The user experience is similar to AuthSub, but OAuth is used far more widely across the worlds of APIs such as Facebook, Twitter, Netflix, and LinkedIn. Once you learn OAuth, you can use it in more places.
  • AuthSub – Redirects a user to an authorization page. After the user authorizes your application to make calls on their behalf, they are redirected back to your application. The application is then supplied with a token used for making API calls on the user's behalf.
  • ClientLogin – Prompts a user for their Google Account credentials, which are then used to make API calls on their behalf. We are discouraging use of ClientLogin in production applications because it has a tendency to train users into building insecure integrated web services.

OAuth is the current recommended best practice for making Google Data API calls on a user's behalf. This document describes how to use three-legged OAuth 1.0 with the Google Data APIs.

When a user authorizes an application to make API calls on their behalf, the following happens:

  1. Your application checks to see if an existing access token is available. Typically, this access token is persisted across sessions. Access tokens provide an access specific to the application and the user, used for making API calls. Most API providers do not expire access tokens, but do provide users with the ability to revoke tokens on a per-application basis. If an access token is available and valid, that's it! You can begin making API calls.
  2. If the access token is either invalid or otherwise unavailable, link or redirect the user to a page hosted at google.com asking for permission. The user looks at the authorization page url to ensure that they are providing their credentials in a secure manner only to Google.com (your application should never deal with Google Account credentials). If they are confident in allowing your application access to their data, the user clicks "Approve". The user returns to your application, which can now access their data.
  3. Make API calls. Profit!

The application side of things is slightly more complex:

  1. Your application requests a request token from the server. The request is signed with your app's consumer key and consumer secret. You obtain these application-specific values from the domain management console.
  2. Using the request token, you redirect the user the authorization page, where the user should authorize your request token.
  3. The user is redirected back to your application. This URL is specified either when you register your application using domain manager console or when the request token is generated. Your request token is promoted to an access token, which can now be persisted indefinitely and used until the user revokes your token.
  4. Make API calls. Profit!

A more detailed description of the "OAuth Dance" can be found in Eric Bidelman's article Using OAuth with the Google Data APIs

In this article, we walk through the process of doing the OAuth dance to acquire a usable access token for a request to Google Data services, and storing the token in the datastore so that it can be reused for returning users.

Using the gdata-python-client Library

Google offers an open source Google Data client library for Python that simplifies token management and requesting data from specific Google Data APIs. This library simplifies development and is used in the code samples scattered throughout the remainder of the article. If you choose to use another library, skip ahead to step 1. The same concepts apply in either case.

To use this library with your Google App Engine application, simply place the library source files in your application's directory, and import them as you usually would. The source directories you need to upload with your application code are src/gdata and src/atom. Then, be sure to call the gdata.alt.appengine.run_on_appengine() function on each instance of a gdata.service.GDataService object. There's nothing more to it than that!.

Note that if you are using GDataClient objects, the run_on_appengine call is not needed.

Step 1: Generating the Access Token Authorization Link

Applications use OAuth to obtain a user's permission for accessing protected Google Data feeds. The process is fairly simple. To request a user's permission to access a protected feed, your app needs to redirect the user to a secure page on google.com where that user can sign in to grant or deny access. After this, the user is redirected back to your app with the newly-granted token stored in the URL. By redirecting to a secure google.com login page, your users don't have to worry that their account credentials are stored insecurely by your application or intercepted and used for nefarious purposes.

Your application needs to specify three things when using OAuth:

  1. the consumer secret and key for your specific application. You can obtain these on the Manage your domains page.
  2. the common base URL for the feeds you want to access
  3. the URL that the user should be redirected to after authorizing your application

We first get a unique request token from Google. Then we use the generate_authorization_url on this token to generate the upgrade link. Then save the request token in the datastore while the user is at google.com. When the user returns, we retrieve the token secret from the datastore and upgrade it to an access token. We then use this access token to access a user's Google Document list.

First, we define generic URL mappings that capture all the URLs and call the script where our handlers live. URL mapping are defined in app.yaml. Here's an example:

application: gdata-feedfetcher
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: main.py

To illustrate this first step of using OAuth in the app, we create a main.py script that looks something like this:

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app, login_required
from google.appengine.api import users
import gdata.gauth
import gdata.docs.client

# Constants included for ease of understanding. It is a more common
# and reliable practice to create a helper for reading a Consumer Key
# and Secret from a config file. You may have different consumer keys
# and secrets for different environments, and you also may not want to
# check these values into your source code repository.
SETTINGS = {
    'APP_NAME': 'gdata-feedfetcher',
    'CONSUMER_KEY': 'INSERT_CONSUMER_KEY_HERE',
    'CONSUMER_SECRET': 'INSERT_CONSUMER_SECRET_HERE',
    'SCOPES': ['https://docs.google.com/feeds/']
    }

# Create an instance of the DocsService to make API calls
gdocs = gdata.docs.client.DocsClient(source = SETTINGS['APP_NAME'])

class Fetcher(webapp.RequestHandler):

    @login_required
    def get(self):
        """This handler is responsible for fetching an initial OAuth
        request token and redirecting the user to the approval page."""

        current_user = users.get_current_user()

        # We need to first get a unique token for the user to
        # promote.
        #
        # We provide the callback URL. This is where we want the
        # user to be sent after they have granted us
        # access. Sometimes, developers generate different URLs
        # based on the environment. You want to set this value to
        # "http://localhost:8080/step2" if you are running the
        # development server locally.
        #
        # We also provide the data scope(s). In general, we want
        # to limit the scope as much as possible. For this
        # example, we just ask for access to all feeds.
        scopes = SETTINGS['SCOPES']
        oauth_callback = 'http://%s/step2' % self.request.host
        consumer_key = SETTINGS['CONSUMER_KEY']
        consumer_secret = SETTINGS['CONSUMER_SECRET']
        request_token = gdocs.get_oauth_token(scopes, oauth_callback,
                                              consumer_key, consumer_secret)

        # Persist this token in the datastore.
        request_token_key = 'request_token_%s' % current_user.user_id()
        gdata.gauth.ae_save(request_token, request_token_key)

        # Generate the authorization URL.
        approval_page_url = request_token.generate_authorization_url()

        message = """<a href="%s">
Request token for the Google Documents Scope</a>"""
        self.response.out.write(message % approval_page_url)


def main():
    application = webapp.WSGIApplication([('/step1', Fetcher)],
                                         debug=True)
    run_wsgi_app(application)

In this example, the URL passed as an oauth_callback argument to get_oauth_token returns the user to our application. The scopes argument indicates which service our app is requesting authorization for. After clicking the link and authorizing your application, you are redirected to the URL specified by oauth_callback with the OAuth request appended to the URL. You then upgrade this token and make calls.

Step 2: Retrieving and Updating a Token

Once an authorization request URL has been generated, we use the token returned to our app to access the feed in question. To do so, we first need to upgrade the request token retrieved in step 1 to a long-lived access token. Let's extend our simple example above to do a few things. We'll oversimplify a bit and consolidate calling the Docs API with the upgrading of the request token in a single block of code. In practice, we want to handle each action in its own handler to better separate responsibilities, but this will do for now.

Below we added the code for the RequestTokenCallback handler to main.py, which demonstrates upgrading to an access token as well as making a Docs API call:

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app, login_required
from google.appengine.api import users
import gdata.gauth
import gdata.docs.client

# Constants included for ease of understanding. It is a more common
# and reliable practice to create a helper for reading a Consumer Key
# and Secret from a config file. You may have different consumer keys
# and secrets for different environments, and you also may not want to
# check these values into your source code repository.
SETTINGS = {
    'APP_NAME': 'gdata-feedfetcher',
    'CONSUMER_KEY': 'INSERT_CONSUMER_KEY_HERE',
    'CONSUMER_SECRET': 'INSERT_CONSUMER_SECRET_HERE',
    'SCOPES': ['https://docs.google.com/feeds/']
    }

# Create an instance of the DocsService to make API calls
gdocs = gdata.docs.client.DocsClient(source = SETTINGS['APP_NAME'])

class Fetcher(webapp.RequestHandler):

    @login_required
    def get(self):
        """This handler is responsible for fetching an initial OAuth
        request token and redirecting the user to the approval page."""

        current_user = users.get_current_user()

        # We need to first get a unique token for the user to
        # promote.
        #
        # We provide the callback URL. This is where we want the
        # user to be sent after they have granted us
        # access. Sometimes, developers generate different URLs
        # based on the environment. You want to set this value to
        # "http://localhost:8080/step2" if you are running the
        # development server locally.
        #
        # We also provide the data scope(s). In general, we want
        # to limit the scope as much as possible. For this
        # example, we just ask for access to all feeds.
        scopes = SETTINGS['SCOPES']
        oauth_callback = 'http://%s/step2' % self.request.host
        consumer_key = SETTINGS['CONSUMER_KEY']
        consumer_secret = SETTINGS['CONSUMER_SECRET']
        request_token = gdocs.get_oauth_token(scopes, oauth_callback,
                                              consumer_key, consumer_secret)

        # Persist this token in the datastore.
        request_token_key = 'request_token_%s' % current_user.user_id()
        gdata.gauth.ae_save(request_token, request_token_key)

        # Generate the authorization URL.
        approval_page_url = request_token.generate_authorization_url()

        message = """<a href="%s">
Request token for the Google Documents Scope</a>"""
        self.response.out.write(message % approval_page_url)


class RequestTokenCallback(webapp.RequestHandler):

    @login_required
    def get(self):
        """When the user grants access, they are redirected back to this
        handler where their authorized request token is exchanged for a
        long-lived access token."""

        current_user = users.get_current_user()

        # Remember the token that we stashed? Let's get it back from
        # datastore now and adds information to allow it to become an
        # access token.
        request_token_key = 'request_token_%s' % current_user.user_id()
        request_token = gdata.gauth.ae_load(request_token_key)
        gdata.gauth.authorize_request_token(request_token, self.request.uri)

        # We can now upgrade our authorized token to a long-lived
        # access token by associating it with gdocs client, and
        # calling the get_access_token method.
        gdocs.auth_token = gdocs.get_access_token(request_token)

        # Note that we want to keep the access token around, as it
        # will be valid for all API calls in the future until a user
        # revokes our access. For example, it could be populated later
        # from reading from the datastore or some other persistence
        # mechanism.
        access_token_key = 'access_token_%s' % current_user.user_id()
        gdata.gauth.ae_save(request_token, access_token_key)

        # Finally fetch the document list and print document title in
        # the response
        feed = gdocs.GetDocList()
        for entry in feed.entry:
            template = '<div>%s</div>'
            self.response.out.write(template % entry.title.text)


def main():
    application = webapp.WSGIApplication([('/step1', Fetcher),
                                          ('/step2', RequestTokenCallback)],
                                         debug=True)
    run_wsgi_app(application)

When the user clicks the "Google Documents" link, they grant the application access to their private data, and they return to the application. Our handler extracts the request use token from the URL, combines it with the token secret stashed in the datastore, and upgrades it to an access token. The application then uses this access token to make API calls on the user's behalf.

Conclusion

Using the Google Data client library for Python, you can easily manage your user's Google Data feeds in your own Google App Engine application.

The Google Data client library for Python includes support for almost all of the Google Data services. For further information, you can read the getting started guide for the library, visit the project to browse the source, and even ask questions on the gdata-python-client's Google group.

As always, for questions about Google App Engine, read our online documentation and explore our community resources.