Gadgets API

Writing OAuth Gadgets

This document describes how to write gadgets that use the OAuth feature to do authentication. You use OAuth in conjunction with the makeRequest() function. For a general discussion of using the makeRequest() function to fetch remote content, see Fetching Remote Content.

This document focuses primarily on the simplest use case: writing gadgets that run on iGoogle and access a user's private data from any website that supports the OAuth protocol. But that is just one way to use OAuth. Ultimately, any OpenSocial gadget running on any container will be able to use the techniques described in this document to access OAuth-protected data. As a container, iGoogle offers the simplest model, because the gadgets a user adds on iGoogle are only visible to that user. Things get more complicated on social networking containers, where the person viewing the gadget may not be the same as the data owner who added the gadget. See the document OAuth Enabled Gadgets on OpenSocial enabled Social Networks for a description of how service providers will support OAuth for social networking containers.

What is the OAuth Proxy?

Many internet services are beginning to offer REST APIs that give access to the private data those internet services store on behalf of a user. For example, an internet service might give users access to their photos, mail, address book, health records, and so on. OAuth is a standard to allow the account owner of the private data to tell the hosting service to allow another website (or gadget), to access the data. The OAuth proxy is designed to make it easier for gadgets to use the OAuth standard.

Note: The OAuth Proxy is only supported in the gadgets.* API for gadgets running in OpenSocial containers. It is not supported for the legacy gadgets API.

Audience

This document is written for gadget developers who want to communicate with their users' services using the OAuth protocol. It assumes you're familiar with the services you're accessing, and are aware of any access/authentication issues involved. You need to know some service-specific details when using the gadgets authentication proxy service.

Contents

  1. What is the OAuth Proxy?
  2. What Internet services have REST APIs that support OAuth?
  3. Key Concepts
  4. Choosing a Service Provider
    1. Google Service Provider
    2. Non-Google Service Provider
  5. Choosing an Endpoint
  6. Sample Gadget Walkthrough
  7. Implementing an OAuth Gadget
    1. Sample Gadget
    2. OAuth Section
    3. Implementing an Approval Flow
    4. More About makeRequest()
  8. Google Data APIs: an Alternative Approach
  9. Other Examples
  10. Advanced OAuth Tricks
    1. Specifying a service provider's OAuth endpoints
    2. Consumer key and callback URL
    3. Finding the Yahoo GUID
    4. Updating the user's presence information
    5. Detecting users who can't use OAuth
    6. Optimizing gadgets

What Internet Services Have REST APIs that Support OAuth?

OAuth is supported by all of the REST APIs available from Google (see OAuth Authentication for Web Applications and Google Data API documentation) as well as MySpace's Data Availability APIs. The OAuth community wiki lists additional service providers that provide OAuth-enabled APIs.

Key Concepts

The table below gives definitions for basic OAuth terms. For more detail, see the OAuth spec and OAuth Authentication for Web Applications.

Term Definition
Service Provider A web application that allows access via OAuth. For example, Google or MySpace.
User An individual who has an account with the service provider, for whom the service provider might have data that could be accessed via OAuth. For example, a gadget might access a user's Google calendar data via OAuth.
Consumer A website or application that uses OAuth to access the service provider on behalf of the user. In this context, a gadget that uses OAuth to access a user's data would be the consumer.
Protected Resources Data controlled by the Service Provider, which the consumer (gadget) can access through authentication.
Container A container is an OpenSocial environment in which gadgets are embedded. For example, iGoogle is a container. The container is responsible for managing the gadgets' layout and controls, as well as for supporting various functionality on behalf of the gadgets. An OAuth gadget can only run in a container that supports OAuth. If a gadget uses OAuth, it is actually the container that executes the OAuth protocol on behalf of the gadget by handling all the digital signing required by the protocol.
Consumer Key A value used by the gadget to identify itself to the service provider. This corresponds to the oauth_consumer_key parameter. See the OAuth spec for details.
Consumer Secret A secret used by the gadget to establish ownership of the consumer key.
Request Token A value used by the gadget to obtain authorization from the user, and exchanged for an access token.
Access Token A value used by the gadget to gain access to the protected resources on behalf of the user, instead of using the user’s service provider credentials.
Token Secret A secret used by the gadget to establish ownership of a given token.

For example, suppose you have a gadget that accesses a user's private calendar data through the Google Data API. In this scenario, Google is the service provider, and the gadget is the consumer. The user is the person using the gadget to access personal calendar data.

The first time the user runs the gadget, the gadget sends the user to the service provider's site, which prompts the user to grant the gadget permission to access the user's calendar data. This authentication is handled by OAuth. Once the user signs into his or her Google account and grants the gadget permission, the gadget is able to access the user's calendar data from that point on (or at least until the user explicitly revokes permission). The user does not have to sign in and grant permission again to access his or her data through the gadget.

Choosing a Service Provider

The first step in creating an OAuth gadget is to choose a service provider. There are two use cases for OAuth:

  • Requesting data from Google Data or another Google service provider.
  • Requesting data from a non-Google service provider.

These options are discussed in more detail below.

Google Service Provider

If you are accessing a Google Data API, all you need to do is specify the Google Data REST API endpoint that you want to access. You specify the endpoint in the <OAuth> section (under the <ModulePrefs> section), and in your gadget's code that uses the endpoint to request data.

Non-Google Service Provider

Accessing a non-Google endpoint typically requires you to register your application with the non-Google service provider (for example see the MySpace registration). If your company wants to expose its own OAuth service provider endpoint, even if only for company-built gadgets, you may find it helpful to refer to the Google-maintained Advanced OAuth Tricks site. 

Once you register your application with a non-Google service provider, that provider gives you an OAuth Consumer Secret that is used to digitally sign all the requests from your application to the service provider. Gadgets are publicly accessible, so they are not a good place to store these types of secrets. Instead, you can register the OAuth Consumer Secret with the website that provides the container in which your gadget will run, assuming the website offers an OAuth Proxy.

In the case of iGoogle, you can send mail to oauthproxyreg@google.com with the following information to register your OAuth Consumer Secret:

  • The URL of your gadget.
  • The OAuth Consumer Key assigned to you by the service provider. The OAuth Consumer Key is the domain identifying the third-party web application. This is the domain used when registering the application with the service provider, so that requests from your application can be identified by the service provider.
  • The OAuth Consumer Secret assigned to you by the service provider. This secret is used to digitally sign all the requests from your application to the service provider.
  • Whether to use symmetric or asymmetric signing with the service provider (or say that you don't know).
  • The OAuth service name attribute your gadget will use to access the key. The value of the OAuth XML service name attribute and the value of the OAUTH_SERVICE_NAME parameter to makeRequest() must match.

Until your OAuth Consumer Secret has been registered, your gadget will not work. If you change the URL of your gadget, you need to re-register the secret for that gadget.

Many OAuth service providers require you to provide your OAuth callback URL when you request an OAuth Consumer Key and Consumer Secret. You can specify a callback URL of http://oauth.gmodules.com/gadgets/oauthcallback for your gadget.

A Note to OAuth Service Providers

If you want to help your development community avoid this manual registration step, you can instead augment your OAuth configuration to accept digital signatures directly from iGoogle. iGoogle currently uses the RSA_SHA1 signature method (as defined in the OAuth standard) and the following public key, presented here as a self-signed certificate that should be easy to import into OAuth libraries implemented in various programming languages:

-----BEGIN CERTIFICATE-----
MIIDBDCCAm2gAwIBAgIJAK8dGINfkSTHMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEG
A1UEChMKR29vZ2xlIEluYzEXMBUGA1UEAxMOd3d3Lmdvb2dsZS5jb20wHhcNMDgx
MDA4MDEwODMyWhcNMDkxMDA4MDEwODMyWjBgMQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBJ
bmMxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDQUV7ukIfIixbokHONGMW9+ed0E9X4m99I8upPQp3iAtqIvWs7XCbA
bGqzQH1qX9Y00hrQ5RRQj8OI3tRiQs/KfzGWOdvLpIk5oXpdT58tg4FlYh5fbhIo
VoVn4GvtSjKmJFsoM8NRtEJHL1aWd++dXzkQjEsNcBXwQvfDb0YnbQIDAQABo4HF
MIHCMB0GA1UdDgQWBBSm/h1pNY91bNfW08ac9riYzs3cxzCBkgYDVR0jBIGKMIGH
gBSm/h1pNY91bNfW08ac9riYzs3cx6FkpGIwYDELMAkGA1UEBhMCVVMxCzAJBgNV
BAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUg
SW5jMRcwFQYDVQQDEw53d3cuZ29vZ2xlLmNvbYIJAK8dGINfkSTHMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAYpHTr3vQNsHHHUm4MkYcDB20a5KvcFoX
gCcYtmdyd8rh/FKeZm2me7eQCXgBfJqQ4dvVLJ4LgIQiU3R5ZDe0WbW7rJ3M9ADQ
FyQoRJP8OIMYW3BoMi0Z4E730KSLRh6kfLq4rK6vw7lkH9oynaHHWZSJLDAp17cP
j+6znWkN9/g=
-----END CERTIFICATE-----

iGoogle uses the OpenSocial specification to provide the URL of the gadget on whose behalf the request is being made to service providers.

Choosing an Endpoint

An endpoint is a URL a gadget uses to access private data through a service provider. See your service provider's documentation to find out what endpoints are available. If your gadget is requesting data through a Google service that supports the Google Data API, see the Google Data APIs documentation for the endpoints supported by each service. Some endpoints require authentication (which you achieve through OAuth), and some of them do not. Here are some examples of Google Data endpoints:

  • http://www.google.com/m8/feeds/contacts/default/full/ -- Get a feed containing the contacts for the currently logged in user.
  • http://gdata.youtube.com/feeds/api/users/default/uploads?alt=json -- Get a JSON-formatted feed containing the uploaded YouTube videos for the currently logged in user.
  • http://gdata.youtube.com/feeds/api/users/username/uploads -- Get a feed containing the YouTube videos for the user specified by username. Note that this does not require authentication.

A gadget can only access data under a certain scope. If a gadget requests access to scope=http://www.google.com/m8/feeds/, the gadget can only use endpoints (feeds) from the Contacts API. A single gadget can request multiple scopes.

Sample Gadget Walkthrough

This document uses a simple example gadget to explain how OAuth gadgets work. The sample gadget fetches and displays the contacts for the currently logged in user. To get a feel for how OAuth gadgets work, step through the following walkthrough.

OAuth gadgets need to be based on the gadgets.* API and run inside of an OpenSocial container that supports OAuth. You can deploy OAuth gadgets in iGoogle.

To add a gadget to iGoogle:

  1. Go to http://www.google.com/ig.
  2. Click the Personalize this page button. This takes you to the iGoogle directory.
  3. Click the add feed or gadget link at the bottom of the left nav bar.
  4. Type the URL for the sample gadget into the text field (http://gadget-doc-examples.googlecode.com/svn/trunk/opensocial-gadgets/oauth-contacts.xml), and click Add.
  5. Click the Back to iGoogle home link (upper left corner) to return to iGoogle and see the sample gadget.

The newly added sample gadget displays a link that says "Personalize this gadget." The link contains a request token. To grant access to your data, follow these steps:

  1. Click the "Personalize this gadget" link.
  2. A Google Accounts page may appear, if you have multiple accounts. This page says "A third party service is requesting permission to access your Google Account." Pick the account for which you want to access data.
  3. You then see the Google Accounts Access Request page. It has text such as "The site www.google.com is requesting access to your Google Account for the product(s) listed below." It lists the products whose data the site (container) wants to access. Click Grant Access. Thus the request token is exchanged for an access token, which lets the gadget fetch your data from the specified service.
  4. Return to iGoogle. At this point the gadget should be displaying your data. For details, see Implementing an Approval Flow.

From this point on, unless you delete the gadget or go to Google Accounts and revoke access, the gadget will be able to continue accessing your data. You only need to grant access one time.

The sample gadget shows one approach for guiding the user through the approval process. Regardless of your particular implementation, your gadget should do the following:

  • Attempt to fetch the user's data.
  • If the fetch succeeds, display the data.
  • If the fetch fails, prompt the user to grant approval with a popup window.
  • When the popup window closes, initiate another fetch to get the user's data. Once the gadget has an associated access token, the data is fetched automatically whenever the gadget runs.

The Shindig project provides JavaScript libraries that you can use to create the popup window and detect when the popup window has closed. For more discussion of this topic, see Implementing an Approval Flow.

Implementing an OAuth Gadget

Once you've decided on a service provider and the endpoints you'll use to fetch the desired data, you're ready to start implementing your gadget.

In addition to its regular gadget features, an OAuth gadget must include the following:

  • A named service provider.
  • An endpoint.
  • An <OAuth...> section inside the <ModulePrefs> section of your gadget that specifies details about any services and endpoints used by the gadget.
  • A mechanism for detecting whether the user has approved access to the data. If the user has not yet granted access, the gadget must provide a way for the user to visit the service provider -- for example, by presenting the user with a link pointing to the service provider's OAuth authorization URL. The service provider will then guide the user through the authentication and approval process. Once the user has approved access to his or her data, the gadget is able to access the user's data.
  • A call to the gadgets.* makeRequest() function with the proper OAuth parameters to fetch the authenticated data.
  • Code for processing the returned data, whatever that means for that particular gadget

In this section, we'll walk through a sample gadget section by section to understand how an OAuth gadget works.

Sample Gadget

This sample gadget fetches the contacts of the currently logged in user via the Google Contacts Data API. It uses the endpoint http://www.google.com/m8/feeds/contacts/default/full, which tells the server to return the contacts for the user whose credentials accompany the request. The contact information is returned as an ATOM XML result in JSON format (http://www.google.com/m8/feeds/contacts/default/full?alt=json). See Using JSON with Google Data APIs for for more information.

Here is the complete gadget. It is discussed in more detail below.

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="OAuth Contacts" scrolling="true">
    <Require feature="locked-domain"/>
    <OAuth>
      <Service name="google">
        <Access url="https://www.google.com/accounts/OAuthGetAccessToken" method="GET" />
        <Request url="https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/" method="GET" />
        <Authorization url="https://www.google.com/accounts/OAuthAuthorizeToken?oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" />
      </Service>
    </OAuth>
  </ModulePrefs>
  <Content type="html">
  <![CDATA[

  <!-- shindig oauth popup handling code -->
  <script src="http://gadget-doc-examples.googlecode.com/svn/trunk/opensocial-gadgets/popup.js"></script>

  <style>
  #main {
    margin: 0px;
    padding: 0px;
    font-size: small;
  }
  </style>

  <div id="main" style="display: none">
  </div>

  <div id="approval" style="display: none">
    <img src="http://gadget-doc-examples.googlecode.com/svn/trunk/images/new.gif">
    <a href="#" id="personalize">Personalize this gadget</a>
  </div>

  <div id="waiting" style="display: none">
    Please click
    <a href="#" id="approvaldone">I've approved access</a>
    once you've approved access to your data.
  </div>

  <script type="text/javascript">
    // Display UI depending on OAuth access state of the gadget (see <divs> above).
    // If user hasn't approved access to data, provide a "Personalize this gadget" link
    // that contains the oauthApprovalUrl returned from makeRequest.
    //
    // If the user has opened the popup window but hasn't yet approved access, display
    // text prompting the user to confirm that s/he approved access to data.  The user
    // may not ever need to click this link, if the gadget is able to automatically
    // detect when the user has approved access, but showing the link gives users
    // an option to fetch their data even if the automatic detection fails.
    //
    // When the user confirms access, the fetchData() function is invoked again to
    // obtain and display the user's data.
    function showOneSection(toshow) {
      var sections = [ 'main', 'approval', 'waiting' ];
      for (var i=0; i < sections.length; ++i) {
        var s = sections[i];
        var el = document.getElementById(s);
        if (s === toshow) {
          el.style.display = "block";
        } else {
          el.style.display = "none";
        }
      }
    }

    // Process returned JSON feed to display data.
    function showResults(result) {
      showOneSection('main');

      var titleElement = document.createElement('div');
      var nameNode = document.createTextNode(result.feed.title.$t);
      titleElement.appendChild(nameNode);
      document.getElementById("main").appendChild(titleElement);
      document.getElementById("main").appendChild(document.createElement("br"));

      list = result.feed.entry;

      for(var i = 0; i < list.length; i++) {
        entry = list[i];
        var divElement = document.createElement('div');
        divElement.setAttribute('class', 'name');
        divElement.appendChild(nameNode);
        if (entry.gd$email) {
          var valueNode = document.createTextNode(entry.gd$email[0].address);
          divElement.appendChild(valueNode);
        }
        document.getElementById("main").appendChild(divElement);
      }
    }

    // Invoke makeRequest() to fetch data from the service provider endpoint.
    // Depending on the results of makeRequest, decide which version of the UI
    // to ask showOneSection() to display. If user has approved access to his
    // or her data, display data.
    // If the user hasn't approved access yet, response.oauthApprovalUrl contains a
    // URL that includes a Google-supplied request token. This is presented in the
    // gadget as a link that the user clicks to begin the approval process.
    function fetchData() {
      var params = {};
      url = "http://www.google.com/m8/feeds/contacts/default/base?alt=json";
      params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
      params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
      params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
      params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
      params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;

      gadgets.io.makeRequest(url, function (response) {
        if (response.oauthApprovalUrl) {
          // Create the popup handler. The onOpen function is called when the user
          // opens the popup window. The onClose function is called when the popup
          // window is closed.
          var popup = shindig.oauth.popup({
            destination: response.oauthApprovalUrl,
            windowOptions: null,
            onOpen: function() { showOneSection('waiting'); },
            onClose: function() { fetchData(); }
          });
          // Use the popup handler to attach onclick handlers to UI elements.  The
          // createOpenerOnClick() function returns an onclick handler to open the
          // popup window.  The createApprovedOnClick function returns an onclick
          // handler that will close the popup window and attempt to fetch the user's
          // data again.
          var personalize = document.getElementById('personalize');
          personalize.onclick = popup.createOpenerOnClick();
          var approvaldone = document.getElementById('approvaldone');
          approvaldone.onclick = popup.createApprovedOnClick();
          showOneSection('approval');
        } else if (response.data) {
            showOneSection('main');
            showResults(response.data);
        } else {
            // The response.oauthError and response.oauthErrorText values may help debug
            // problems with your gadget.
            var main = document.getElementById('main');
            var err = document.createTextNode('OAuth error: ' +
              response.oauthError + ': ' + response.oauthErrorText);
            main.appendChild(err);
            showOneSection('main');
        }
      }, params);
    }
    // Call fetchData() when gadget loads.
    gadgets.util.registerOnLoadHandler(fetchData);
  </script>
  ]]>
  </Content>
</Module>

Note: We recommend that you opt into the locked-domain feature by including the line <Require feature="locked-domain"/> in your gadget. We also recommend that you host your own copy of popup.js.

<OAuth> Section

The first thing your gadget needs to include is an <OAuth> section inside of the <ModulePrefs> section:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="OAuth Contacts" scrolling="true">
    <Require feature="locked-domain"/>
    <OAuth>
      <Service name="google">
        <Access url="https://www.google.com/accounts/OAuthGetAccessToken" method="GET" />
        <Request url="https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/" method="GET" />
        <Authorization url="https://www.google.com/accounts/OAuthAuthorizeToken?oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" />
      </Service>
    </OAuth>
  </ModulePrefs>

The OAuth section supplies the container with the OAuth service configuration for the gadget. For more detailed descriptions of these fields, see the Reference.

Element Description
/ModulePrefs/OAuth/Service This element is a single OAuth service configuration.

Attribute:
  • name -- The name of the service, used for referencing OAuth services at runtime. This parameter is optional. and if unspecified, defaults to "". Gadget developers specify which OAuth service they wish to use by passing the service name as a parameter to makeRequest().
/ModulePrefs/OAuth/Service/Request
/ModulePrefs/OAuth/Service/Access
These elements represent the OAuth request token and access token URLs. See the OAuth spec and OAuth Authentication for Web Applications for details.

Attributes:
  • url -- The URL for the endpoint.
  • method -- The HTTP verb to use for making the request. This parameter is optional. If unspecified, it defaults to POST.
  • param_location -- One of 3 possible locations in the request to the service where the OAuth parameters may be passed. You can use this value to specify the location of OAuth-related parameters. The possible values correspond to the options described in the OAuth spec:
    • uri-query -- OAuth parameters are passed in the query string.
    • auth-header (default) -- OAuth parameters are passed in the Authorization header.
    • post-body -- OAuth parameters are passed in the body of the POST request.
/ModulePrefs/OAuth/Service/Authorization The OAuth authorization URL. When your gadget needs to ask a user's approval to access his or her data, it opens a popup window to this URL.

The authorization URL includes an oauth_callback query parameter. The OAuth service provider redirects to this URL after the user has approved access to his or her data. You should specify http://oauth.gmodules.com/gadgets/oauthcallback as your oauth_callback URL. The oauthcallback page includes a snippet of JavaScript that automatically closes the popup window. Your gadget can detect when the popup window closes and attempt to fetch the user's data.

Implementing an Approval Flow

An OAuth gadget fetches data for an authenticated user. To do this, the user must grant permission for the service (in this example, Google Data Contacts) to share the user's data with the gadget.

For example, consider the contacts sample gadget. This is the sequence of events:

  1. When the gadget runs, it calls gadgets.util.registerOnLoadHandler(fetchData), which invokes the fetchData() function.
  2. The fetchData() function calls makeRequest().
  3. The makeRequest() function specifies a callback parameter. When the callback is invoked, it is passed a JavaScript object that includes several OAuth-specific fields in addition to the normal values returned by makeRequest().

Here is the fetchData() function:

function fetchData() {
  var params = {};
  url = "http://www.google.com/m8/feeds/contacts/default/base?alt=json";
  params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
  params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
  params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
  params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
  params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;

  gadgets.io.makeRequest(url, function (response) {
    if (response.oauthApprovalUrl) {
      // Create the popup handler. The onOpen function is called when the user
      // opens the popup window. The onClose function is called when the popup
      // window is closed.
      var popup = shindig.oauth.popup({
        destination: response.oauthApprovalUrl,
        windowOptions: null,
        onOpen: function() { showOneSection('waiting'); },
        onClose: function() { fetchData(); }
      });
      // Use the popup handler to attach onclick handlers to UI elements.  The
      // createOpenerOnClick() function returns an onclick handler to open the
      // popup window.  The createApprovedOnClick function returns an onclick
      // handler that will close the popup window and attempt to fetch the user's
      // data again.
      var personalize = document.getElementById('personalize');
      personalize.onclick = popup.createOpenerOnClick();
      var approvaldone = document.getElementById('approvaldone');
      approvaldone.onclick = popup.createApprovedOnClick();
      showOneSection('approval');
    } else if (response.data) {
        showOneSection('main');
        showResults(response.data);
    } else {
        // The response.oauthError and response.oauthErrorText values may help debug
        // problems with your gadget.
        var main = document.getElementById('main');
        var err = document.createTextNode('OAuth error: ' +
          response.oauthError + ': ' + response.oauthErrorText);
        main.appendChild(err);
        showOneSection('main');
    }
  }, params);
}

When the gadget processes the callback, it first checks to see whether response.oauthApprovalUrl has a non-null value. If the user has not yet granted access to his or her data, response.oauthApprovalUrl contains a URL that the user needs to visit to give the gadget access to the data. Part of that URL is a request token issued by the service provider (in this case, Google).

The gadget first creates a shindig.oauth.popup object to manage the popup window. The shindig.oauth.popup object takes several parameters:

var popup = shindig.oauth.popup({
  destination: response.oauthApprovalUrl,
  windowOptions: null,
  onOpen: function() { showOneSection('waiting'); },
  onClose: function() { fetchData(); }
});

The destination parameter specifies the URL the popup window will open.

The windowOptions parameter specifies the options to pass to the browser-supplied window.open function. You can control the size, placement, and chrome used by the popup window. Different browsers support different parameters to window.open. For example, see the documentation for Internet Explorer and Firefox.

The onOpen function will be called when the user clicks a link to open the popup. This gadget calls showOneSection('waiting') to display an appropriate message while waiting for the user to approve access.

The onClose function will be called when the popup window is closed. The gadget registers a call to fetchData() to retrieve the user's data once the popup window has closed.

After creating the popup object, onclick handlers must be associated with DOM elements to trigger the opening of the window. The gadget sets the onclick handler for the personalize HREF inside the approval <div> to open the popup window:

var personalize = document.getElementById('personalize');
personalize.onclick = popup.createOpenerOnClick();

The gadget also configures the approvaldone HREF in the waiting <div> to the popup.createApprovedOnClick() handler. When the approvaldone HREF is clicked, the gadget closes the popup window and attempts to fetch the user's data. Hopefully the user will never need to click the I've approved link, but just in case, you should attach the onclick handler:

var approvaldone = document.getElementById('approvaldone');
approvaldone.onclick = popup.createApprovedOnClick();

The gadget displays the approval <div> to ask the user to click a link to open the popup window. Many browsers use popup blockers to prevent unwanted popup windows. To avoid being blocked by a popup blocker, your gadget should not open a popup window until the user has clicked a button or link.

showOneSection('approval');

The gadget uses <div>'s and the showOneSection() function to display the appropriate UI, depending on the gadget's approval state. There are three <div>'s, one for each possible state of the gadget:

  • approval -- If the user has not yet granted access, the gadget uses the approval <div> to display a UI with a "Personalize this gadget" link that contains the request token. The user clicks this link to begin the approval process.
  • waiting -- The gadget displays this <div> if the user has opened the popup window but hasn't yet approved access. The gadget displays text prompting the user to confirm that s/he approved access to data. The user may not ever need to click this link, if the gadget is able to automatically detect when the user has approved access, but showing the link gives users an option to fetch their data even if the automatic detection fails. If automatic detection fails, the gadget displays the message "Please click I've approved access once you've approved access to your data." When the user clicks, the gadget calls fetchData() to fetch the user's data.
  • main -- Once an access token is in place, the gadget uses the main <div> to display the user's data whenever the gadget runs. This <div> is also used to display any errors.
<div id="main" style="display: none">
  </div>

  <div id="approval" style="display: none">
    <img src="http://gadget-doc-examples.googlecode.com/svn/trunk/images/new.gif">
    <a href="#" id="personalize">Personalize this gadget</a>
  </div>

  <div id="waiting" style="display: none">
    Please click
    <a href="#" id="approvaldone">I've approved access</a>
    once you've approved access to your data.
  </div>

  <script type="text/javascript">
    function showOneSection(toshow) {
      var sections = [ 'main', 'approval', 'waiting' ];
      for (var i=0; i < sections.length; ++i) {
        var s = sections[i];
        var el = document.getElementById(s);
        if (s === toshow) {
          el.style.display = "block";
        } else {
          el.style.display = "none";
        }
      }
    }

More About makeRequest()

The makeRequest() function is described in detail in Fetching Remote Content. You use it to retrieve and operate on remote web content. It takes the following arguments:

  • String url - The URL where the content is located
  • Function callback - The function to call with the data from the URL once it is fetched
  • Map.<gadgets.io.RequestParameters, Object> opt_params - Additional parameters to pass to the request.

If opt_params[gadgets.io.RequestParameters.AUTHORIZATION] is set to gadgets.io.AuthorizationType.OAUTH, the container needs to use OAuth to gain access to the resource specified in the request. This typically requires the gadget to obtain the user's content by directing the user to the service provider to gain access.

Optional Parameters

You can specify the these additional OAuth parameters in opt_params:

Parameter Descripton
gadgets.io.RequestParameters.OAUTH_SERVICE_NAME The nickname the gadget uses to refer to the OAuth <Service> element from its XML spec. The service name can also be specified in the /ModulePrefs/OAuth/Service XML section under <ModulePrefs>. If unspecified in either place, defaults to "".
gadgets.io.RequestParameters.OAUTH_TOKEN_NAME The nickname the gadget uses to refer to an OAuth token granting access to a particular resource. If unspecified, defaults to "". Gadgets can use multiple token names if they have access to multiple resources from the same service provider. For example, a gadget with access to a contact list and a calendar might use a token name of "contacts" to use the contact list token, and a contact list of "calendar" to use the calendar token.
gadgets.io.RequestParameters.OAUTH_REQUEST_TOKEN A service provider may be able to automatically provision a gadget with a request token that is preapproved for access to a resource. The gadget can use that token with the parameter. This parameter is optional.
gadgets.io.RequestParameters.OAUTH_REQUEST_TOKEN_SECRET The secret corresponding to a preapproved request token. This parameter is optional.
gadgets.io.RequestParameters.OAUTH_USE_TOKEN

This parameter can be either "never", "if_available", or "always". Some service provider APIs to do not require an OAuth access token. Such APIs authenticate the calling application via the OAuth consumer key, and then allow the request to proceed. You can set OAUTH_USE_TOKEN to "never" to avoid unnecessarily requesting the user's approval to access such an API.

Some OAuth APIs provide limited data if a user has not granted approval, but can offer additional functionality if a user has granted the calling application an access token. If you are using an API that can accept an OAuth access token, but does not require the token, you can set OAUTH_USE_TOKEN to "if_available". If the user has already approved access, the access token will be sent. If the user has not approved access, the request will proceed without an access token.

Many OAuth APIs only function if an access token is sent. You must ask for the user's approval before using such an API. Set OAUTH_USE_TOKEN to "always" to require that an access token be available. If no access token is available, the approval URL will be returned to your gadget.

If you set AUTHORIZATION to SIGNED, the default value for OAUTH_USE_TOKEN is "never". If you set AUTHORIZATION to OAUTH, the default value for OAUTH_USE_TOKEN is "always".

If OAuth is used, the container executes the OAuth protocol on behalf of the gadget. As explained in A Note to OAuth Service Providers, if the gadget has not registered a consumer key for use with iGoogle, iGoogle uses its default RSA signing key.

The Callback Parameter

The makeRequest() callback parameter is passed a JavaScript object with several OAuth-specific fields in addition to the normal values returned by makeRequest():

OAuth Field Description
oauthApprovalUrl If this value is specified, the value is a URL that includes a request token issued by the service provider. A non-null value means that the user needs to visit an external page to approve the gadget's request to access data. Use of a pop-up window to direct the user to the external page is recommended. Once the user has approved access, the gadget can repeat the makeRequest() call to retrieve the data.
oauthError If this value is specified, it indicates an OAuth-related error occurred.
oauthErrorText If this value is specified, it indicates an OAuth-related error occurred. The value can be used to provide debugging information for gadget developers. Parameters:
  • String url -- The URL where the content is located.
  • Function callback --- The function to call with the data from the URL once it is fetched.
  • Map.<gadgets.io.RequestParameters, Object> opt_params -- Additional request parameters or proxy request parameters.

OpenSocial Extensions to OAuth

OpenSocial containers can provide additional information about the context of the request to the OAuth service provider site by passing additional signed parameters in the OAuth request. The additional parameters, such as opensocial_owner_id and opensocial_app_url, are described in the gadgets.io.makeRequest documentation. Service providers that are OpenSocial-aware can use these parameters to return additional data to the user. For example, a service provider could return information about not only the viewer of the gadget, but also about the viewer's friends.

Google Data APIs: an Alternative Approach

The example above uses the gadgets.* makeRequest() method to fetch data. But if you are using the Google Data APIs, you can take advantage of a Google Data JavaScript library that works with the OAuth Proxy. It calls makeRequest() behind the scenes, so the end effect in your gadget is the same.

For details and an example, see the article Creating a Google Data Gadget.

Other Examples

Here are some other sample OAuth gadgets to help you get started:

Advanced OAuth Tricks

This section lists some additional tips and tricks that can help you write better gadgets. To illustrate these techniques, this section uses a "Yahoo Presence Gadget" sample gadget that accesses Yahoo's status API via OAuth. This section covers the following topics:

For other advanced tricks you can use with OAuth, see OAuth & Federated Login Research.

Specifying a service provider's OAuth endpoints

Each OAuth service provider must expose the following URLs used to implement the OAuth protocol:

  • Request token URL
  • Access token URL
  • Authorization URL

The service provider typically publishes this information in the documentation for their OAuth-enabled APIs. In addition, the service provider must decide whether their request token and access token URLs should be accessed using the GET or POST HTTP method, and whether OAuth parameters should be passed in the request query string, the HTTP Authorization header, or in the body of a POST request.

To learn more about Google's implementation of OAuth, see OAuth Authentication for Web Applications
To learn more about Yahoo's implementation of OAuth, see OAuth Authorization Flow

Once you determine this information, you need to enter it into the gadget in the <OAuth> element. Our sample gadget uses the following <OAuth> configuration to access Yahoo's OAuth APIs:

 <OAuth>
   <Service name="yahoo">
     <Request
       param_location="uri-query"
       url="https://api.login.yahoo.com/oauth/v2/get_request_token" />
     <Access
       param_location="uri-query"
       url="https://api.login.yahoo.com/oauth/v2/get_token" />
     <Authorization url="https://api.login.yahoo.com/oauth/v2/request_auth" />
  </Service>
</OAuth>

Note that the <Request> element and <Access> element both have the attribute param_location set to "uri-query". That tells the gadget server that OAuth parameters should be passed in the query string. Here are the possible values for the param_location attribute:

  • uri-query: OAuth parameters are passed in the query string
  • auth-header: OAuth parameters are passed in the Authorization header
  • post-body: OAuth parameters are passed in the body of the POST request

The default value for param_location is "auth-header", since most OAuth service providers support passing OAuth pararameters in the Authorization header. To pass OAuth parameters in the POST body, you must specify that an HTTP method of POST should be used for OAuth requests by including a "method" attribute on the <Request> and <Access tags>. For example:

 <Access
   param_location="post-body"
   method="POST"
   url="https://www.example.com/request_token" />

Consumer Key and Callback URL

Most OAuth service providers require that you register for a developer key (called an OAuth consumer key) before you are allowed to use their OAuth APIs. For our sample gadget, which uses the Yahoo Presence API, we need to register our OAuth consumer key at the Yahoo! Developer Network. The OAuth consumer key is used to authenticate your application. Some OAuth service providers let you choose between a consumer key for a desktop application versus a consumer key for a web application. The difference is important, because in many cases your application can provide a better user experience if you use a web application consumer key.

Web application consumer keys are intended for use by applications running in a web browser. When the user wants to approve access for your application, your application will pass an oauth_callback parameter on the authorization URL. After the user has granted access, the service provider redirects the user to your oauth_callback URL to signal that authorization is complete.

Desktop application keys are intended for use by rich client applications that are not running in a web browser. Some OAuth service providers will not redirect to an oauth_callback URL if a desktop consumer key is used. Instead the user needs to manually indicate that authorization is complete.

If a web application consumer key is better, why would you ever ask for a desktop application key for your gadget? Some OAuth service providers are not willing to grant a web application consumer key unless you first prove ownership of the domain used for your oauth callback URL. If you do not have a web hosting service, you may not be able to get a web application consumer key.

There are the following possibilities for your OAuth callback URL, depending on the OAuth service provider and whether you can host HTML files yourself:

  • Use a Google-provided OAuth callback URL: If the OAuth service provider allows you to specify an arbitrary oauth_callback URL, great! Use http://oauth.gmodules.com/gadgets/oauthcallback as your oauth_callback. When the user is redirected to that URL, the popup window will close automatically. Your gadget should detect that the popup has closed and automatically fetch the user's data.
  • Use your own OAuth callback URL: If the OAuth service provider requires that you use an oauth_callback URL on a domain you own, you can host a small static HTML file to act as your OAuth callback. The following HTML will close the popup window so your gadget can detect that approval was granted:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  <html>
    <head>
      <title>Close this window</title>
    </head>
    <body>
      <script type="text/javascript">window.close();</script>
      Close this window.
    </body>
  </html>
  • Don't use any callback URL: If the service provider will not let you provide an oauth_callback URL, you can still write a gadget. After the user opens the popup window to grant approval, you should display a message to the user letting them know to click a button in the gadget once they've approved access. You can also detect when the popup window has closed and automatically fetch the user's data then.

Once you've received your consumer key and secret from the service provider, you need to provide the key to Google so we can install it for your gadget. Send mail to oauthproxyreg@google.com with the following information to register your OAuth Consumer Secret:

  • The URL of your gadget.
  • The OAuth Consumer Key assigned to you by the service provider. The OAuth Consumer Key is the domain identifying the third-party web application. This is the domain used when registering the application with the service provider, so that requests from your application can be identified by the service provider. For Yahoo! APIs, the consumer key is a long ASCII string.
  • The OAuth Consumer Secret assigned to you by the service provider. This secret is used to digitally sign all the requests from your application to the service provider. Yahoo calls this the "shared secret."
  • Whether to use symmetric or asymmetric signing with the service provider (or say that you don't know).

Until your OAuth Consumer Secret has been registered, your gadget will not work. If you change the URL of your gadget, you need to re-register the secret for that gadget.

Finding the Yahoo GUID

During the OAuth approval flow a client makes a request for an OAuth access token and token secret. OAuth gadgets never make this request explicitly; for both security and convenience all secrets are stored on the server. This works well with most OAuth service providers. However, some service providers choose to include additional data about the user along with the normal access token response. If your gadget needs access to this extra data, you need to make the access token request yourself.

For example, Yahoo's OAuth APIs require that you include the user's Yahoo GUID in the request URL. The GUID is returned as the xoauth_yahoo_guid parameter on the access token response from Yahoo. The gadget first needs to request the access token URL to retrieve the Yahoo GUID, and then needs to save the GUID as a user preference for the gadget. Subsequent requests for user data insert the GUID into the request URL.

The gadget code first checks to see whether the GUID is already known:

 function initGadget() {
   ...
   if (!getGuid()) {
     // First time use of the gadget, we don't know the GUID.
     completeAuthorization();
   } else {
     // We know the GUID, fetch the status message.
     fetchStatus();}
   ...
}

The completeAuthorization method then sends a request to the access token endpoint:

function completeAuthorization() {
   var params = getDefaultParams();var url = "https://api.login.yahoo.com/oauth/v2/get_token";
   log("Completing authorization");
   gadgets.io.makeRequest(url, function(response) {
   makeRequestCallback(authorizationDoneCallback, response);
   }, params);
}

This request will fail to retrieve the GUID the first time the user views the gadget, because the user needs to grant approval. The gadget asks the user to approve access in a popup window, as described in Implementing an Approval Flow:

popup = shindig.oauth.popup({
   destination: response.oauthApprovalUrl,
   windowOptions: "width=800,height=600",
   onOpen: function() { showOneSection('waiting'); },
   onClose: function() { completeAuthorization(); }
});

Note that the completeAuthorization method is registered to run when the popup window closes. When the popup window closes the completeAuthorization method runs. The server then requests an access token and token secret from Yahoo. The Yahoo GUID is also retrieved. The OAuth secrets are stored on the server for security reasons, but other parameters (any parameter on the response that does not begin with "oauth") are returned to the gadget. The gadget then saves the GUID as a user preference for later use:

function authorizationDoneCallback(data) {
   var guid = data.xoauth_yahoo_guid;
   prefs.set("guid", guid);
   fetchStatus();
}

Note: To save the value of a userpref, you need to include the setprefs feature in your gadget (<Require feature="setprefs"/>) see Saving State for more information.

The last step is to determine the URL to use for requesting the user's data. Our sample gadget builds the URL for the Presence endpoint like this:

function getPresenceUrl() {
   if (!getGuid()) {
   throw "Presence API doesn't work without GUID";
   }
   return "http://social.yahooapis.com/v1/user/" + getGuid() +
   "/presence/presence?format=json";
   }

When building a gadget to contact an OAuth service provider that returns user metadata with the access token response, be aware that you must request this data immediately after the user has granted approval for your gadget, before you make any other OAuth requests. The metadata is not stored on the server, so you must be sure to request it at the same time as the server retrieves the access token.

Updating the user's presence information

REST APIs frequently require that you PUT or POST to update a user's information. Sending a PUT request with gadgets.io.makeRequest is simple: just specify the method, content-type, and request body along with the other request parameters. The following code snippet updates a user's status message by sending a PUT request with a JSON request body:

function updateStatus() {
   currentStatus = $('status').value;
   var url = getPresenceUrl();
   var params = getDefaultParams();
   params.METHOD = "PUT";
   params.HEADERS = { "Content-Type": "application/json" };
   params.POST_DATA = gadgets.json.stringify({
     "status": currentStatus
   });
   log("Updating status");
   gadgets.io.makeRequest(url, function(response) {
     makeRequestCallback(null, response);
   }, params);
}

Detecting users who can't use OAuth

Not all iGoogle users are able to use OAuth gadgets, because not all iGoogle users are actually logged in to Google. A user can create an iGoogle home page and install gadgets without first creating a username and password. These users (sometimes called "cookied users," because they are identified only by cookie and not by user ID) can't use OAuth gadgets. Your gadget should detect when a user is not logged in by checking for errors in the response for an OAuth-enabled feed. If the response "oauthError" parameter is "UNAUTHENTICATED", the user is not fully logged in to iGoogle. You should prompt those users to log in.

function makeRequestCallback(nextCallback, response) {
   var popup = null;
   if (response.oauthApprovalUrl) {
     ... approval needed ...
   } else if (response.data) {
     ... data returned ...
   } else if (response.oauthError === "UNAUTHENTICATED") {
     showError(
     "Please sign in to your iGoogle Account to use this gadget.");
   } else {
     ... other error condition ...
   }
}

The OAuth proxy is not available in all countries yet, so if you are releasing your gadget soon you should also make sure that the new gadgets APIs are available to your user. You need to make this check very early on in the gadget loading process, before you attempt to use any of the new APIs. The best way to check for API availability is to look if the gadgets.util namespace is defined:

if (!gadgets.util) {
  showError('Sorry, iGoogle does not support a feature we need in this country yet. Stay tuned.');
} else {
  // Get the gadget started.
  gadgets.util.registerOnLoadHandler(initGadget);
}

Optimizing Gadgets

Minimizing latency is critical to creating a good user experience for your gadget. You can use authenticated <Preload> tags to allow your gadget to be rendered without extra round trips to the server. The data will be fetched during the initial gadget rendering process and included in the first response from the server. When your gadget calls gadgets.io.makeRequest the inlined data will be returned.

The following Preload tag is used to fetch a user's status message from the Yahoo! Presence API.

<Preload
   href="http://social.yahooapis.com/v1/user/__UP_guid__/presence/presence?format=json"
   authz="OAUTH"
   oauth_service_name="yahoo"
   sign_owner="false"
   sign_viewer="false" />

The user's GUID is automatically substituted in to the URL using hangman variable subsitution. The __UP_guid__ text is replaced with the value of the "guid" user preference. The <Preload> tag takes almost all of the same parameters as gadgets.io.makeRequest(), except that HTTP method and body cannot be specified for a Preload. Preload requests are always sent using HTTP GET.

You don't need to change your gadget JavaScript to add a Preload; just make sure that the href tag in the <Preload> matches the URL your gadget will need to fetch.

Back to top

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.