Givealink API Developer Guide

This guide is addressed to developers of applications using Givealink data through the public Givealink API. It provides some guidelines and snippets of code covering the main steps of OAuth authorization protocol that manages the access to protected data. To fully understand the examples, this guide supposes a basic understanding of the OAuth terminology and protocol (refer to this link for some useful definitions).

In particular, we provide:

  1. Obtaining an Access Token following the Oauth protocol workflow .
    This example covers the steps that a web-based application has to follow to obtain a pair of authorized Access Token and Access Secret. The example adopts the Ruby on Rails web framework and the Ruby library Ruby OAuth GEM as reference platform. Additional examples and resources in a wide spectrum of languages can be downloaded from the code section of the Oauth web site.
  2. Accessing protected resources (Ruby OAuth library example)
    This example shows how to call a public or private API method exploiting the Ruby library Ruby OAuth GEM.
  3. Accessing protected resources (Python example)
    A step-by-step python example of how to build a request to a protected API method. This example provides a template for implementing generic consumers without the support of language-dependent OAuth libraries.

Obtaining an Access Token following the Oauth protocol workflow.

Getting Started as a Developer

The first step for a developer is to register an application, obtaining an Application Key and an Application Secret (i.e., Consumer Key and Consumer Secret in OAuth jargon). As shown below, the developer has to specify a name (e.g., givealink_consumer_example), the main url of the application (e.g., http://www.example.com) and a callback url (e.g., http://www.example.com/callback) where the Service Provider will redirect the application after the user's authorization phase.

Register_consumer

When a Consumer is registered, a summary (see image below) shows the application key and secret with the urls where the Givealink OAuth provider makes available the functionalities for granting and authorizing tokens during the protocol chain.

Obtained_keys_consumer

Obtaining an Unauthorized Request Token

Before starting with the OAuth authorization workflow, we suppose that a Rails application has been created (e.g., rails givealink_consumer_example) and the Ruby OAuth library successfully installed (e.g., sudo gem install oauth). It is important to underline that this library is intended to be used in creating Ruby Consumer and Service Provider applications but it is NOT a Rails plugin. Indeed, it could easily be used for the foundation for such a plugin. As a matter of fact, it has been pulled out from the OAuth Rails Plugin which now requires this GEM.

This example provides a basic skeleton of a web-based Consumer and it does not cover specific implementation issues like tokens management through models or views rendering. Due to the explanatory nature of this example, we use some basic design choices that can be easily improved in a production environment.

Let us start with the implementation of a Rails controller (e.g., consumer_controller.rb) including the Ruby OAuth library.

  1   require 'rubygems'
  2   require 'oauth'
  

For simplicity, we defined the get_current_consumer method:

  
  1   def get_current_consumer

  2     application_key = "7clbYyS7X3GYDeGeiGOh"
  3     application_secret = "xnAxWyvec2LsOZymVJYXH7wvIobfFB5GdSz6Zevc"

  4     provider_url = "http://www.givealink.org"

  5     consumer = OAuth::Consumer.new(
                     application_key, application_secret,
                     :site => provider_url,
                     :request_token_path => "/oauth/request_token",
                     :authorize_path => "/oauth/authorize",
                     :access_token_path => "/oauth/access_token",
                     :http_method => :get)
   6    consumer
   
   7  end
  
  

It returns an instance of an OAuth::Consumer from the application key and secret, the provider url, the location of the end-points implementing the OAuth functionalities, and other complementary parameters (see the OAuth library documentation for more details).

In a similar way, we defined the method get_callback_url that provides the callback url.

The first step is the implementation of an action (e.g., get_request_token) to retrieve an Unauthorized Request Token:

  1   def get_request_token

  2     request_token = get_current_consumer.get_request_token(:oauth_callback => get_callback_url)

  3     session[:request_token] = request_token.token
  4     session[:request_token_secret] = request_token.secret

  5   end
  

An instance of a consumer is created (line 2) by the method get_current_consumer. Afterwards, the controller sends a request to the provider asking for the generation of a new Request Token (line 2) using the method get_request_token. The parameter :oauth_callback is mandatory for a web-based consumer, and it specifies the url where to redirect the application after the user authorization phase (see next step). At the end, the token and the secret are stored in the session for future use (lines 3-4).

Obtaining User Authorization

After regenerating the Request Token using the data stored in the session (line 2), the Consumer redirects the user to the Service Provider in order to obtain approval for future access to his protected data. The target url is built calling the method authorize_url and appending the callback parameter.

  1   def authorize_user

  2     request_token = OAuth::RequestToken.new(
                            get_current_consumer,
                            session[:request_token],
                            session[:request_token_secret])
  3     redirect_to request_token.authorize_url + "&oauth_callback=" + CGI.escape(get_callback_url)

  4   end
  

If the user is not logged in, the Service Provider asks for user credentials in order to authenticate the user (see image below).

Oauth_login

Afterwards, the Service Provider asks for user authorization to grant access to protected data from the consumer application (givealink_consumer_example in our case).

Oauth_approval

Obtaining an Access Token

In the case of approval, the Service Provider redirects to the url specified by the callback_url parameter. In this example, it means the execution of the controller action callback listed below.

The Consumer exchanges the Authorized Request Token for an Access Token (line 3) using the get_access_token method. The Access Tokens allow consumers to invoke API methods on behalf of the user. Note that the oauth_verifier parameter is mandatory if the provider is compliant with the OAuth v1.0a specification. The validity of the Access Token depends on the provider policy. In any case a user can revoke the grant whenever he wants just visiting the My API Account section in the Givalink web site.

    1 def callback

    2    request_token = OAuth::RequestToken.new(
                  get_current_consumer,
                  session[:request_token],
                  session[:request_token_secret])

    3    access_token = request_token.get_access_token(
                      :oauth_verifier => params[:oauth_verifier])

    4    session[:access_token] = access_token.token
    5    session[:access_token_secret] = access_token.secret

    6 end
  

Accessing protected resources (Ruby OAuth library example)

After the user authorization protocol described in the previuos example, a Consumer holds an Access Token and an Access Secret that allow the Consumer to access protected data on behalf of the user. The method access_protected_data shows the steps for calling Givealink API methods.

After regenerating an Access Token instance, it is sufficent to invoke the get method passing the url of the API service to be invoked. The Ruby OAuth GEM library manages signatures, nonces and timestamps, and the HTTP communication. Moreover, it allows requests with other HTTP methods, e.g., POST, DELETE. For more details, refer to the documentation.

  1 def access_protected_data

  2    access_token = OAuth::AccessToken.new(
          get_current_consumer,
          session[:access_token],
          session[:access_token_secret])

  3    resp = access_token.get('/api/2.0/?method=user.getAnnotations&user=test@gmail.com&api_key=7clbYyS7X3GYDeGeiGOh')

  4    render :xml => resp.body

  5 end
  

Try the example

To test the example, first download the consumer_controller.rb Rails controller, then follow the instructions below:

  1. Create a new Rails application: rails givealink_oauth_example
  2. Create a new controller: ./script/generate controller consumer
  3. Overwrite the consumer_controller.rb in app/controllers with the downloaded file
  4. Edit the consumer_controller.rb file specifying the correct values for:
    1. application_key and application_secret: remember that you have to register your consumer at the Givealink site.
    2. callback_url: specify the url where your consumer is redirected when the Request Token is authorized (e.g., http://www.example.com/callback). Remember that if you modify the last part, e.g.,http://www.example.com/my_callback, you have to modify the controller action name accordingly.
    3. In the access_protected_data method modify the user parameter in the requested url specifying your GiveALink username. Of course, you can change HTTP method (e.g., post) or method invoked (e.g., '/api/2.0/?method=tag.getTopTags')
  5. Upload the view callback.erb in app/views/consumer
  6. Open a browser at http://www.example.com/consumer/oauth_workflow (view the page source if you don't see the generated xml, e.g., in Safari)

Accessing protected resources (Python example)

This example shows a Python script that accesses to a protected API method (i.e., url.getInfo) without the use of a third-party library. It aims to show the basic steps for signing requests according to the OAuth specifications (see here for details). Of course, it is just a possible implementation that, due to his illustrative nature, could be refined in many ways.

    import urllib2
    import urllib
    import time
    import hashlib
    import hmac
    import base64

    if __name__ == "__main__":

        consumer_key = "8bmpYlss53FNZaFBhQ9z"
        consumer_secret = "ntqvxyzNWbWxrw8qcJ3xiOVvvgUHTRpYELdBolA6"

        access_token = "DtGe1pe7NUtnNcgvNjBR"
        access_secret = "NOuqBlqhhEtVwwHZtsjjHkIbNBGDLCnLC7uK2C1T"

        nonce = "i0si5IXhKmHIkorbEIGJoT84UkwKqmFS6pxzN0wcIQ"
        timestamp = str(int(time.time()))

        signature_method = "HMAC-SHA1"
        oauth_version = "1.0a"

        scheme = "http"
        port = 3000
        authority = "localhost"
        path = "/api/2.0/"

        method = "GET"

        oauth_params = {
                        'oauth_consumer_key': consumer_key,
                        'oauth_token': access_token,
                        'oauth_nonce': nonce,
                        'oauth_timestamp': timestamp,
                        'oauth_signature_method': signature_method,
                        'oauth_version': oauth_version
                    }

        url_params = { 'method': "url.getInfo",
                        'user': 's.rossano@inwind.it',
                        'url': "http://www.wordreference.com",
        }

        params = dict()
        params.update(oauth_params)
        params.update(url_params)

    #  UTF-8 ENCODING and URL-ECONDING
        url_encoded_params = dict()
        for param in params.keys():
            url_encoded_params[urllib.quote_plus(param.encode("utf-8"))] =
                                           urllib.quote_plus(params[param].encode("utf-8"))

    #  Order the parameters on a key-based criterion. For simplicity this example does not consider
    #  multiple parameters with the same key. In this case, the specification suggests to use values to
    #  discriminate between same keys.

        sorted_params = url_encoded_params.keys()
        sorted_params.sort()

    #  Normalizing parameters
        normalized_params = str()
        for current in sorted_params:
            normalized_params += (current+"="+url_encoded_params[current]+"&")

        normalized_params = normalized_params[:-1]

    #  Normalizing URL
        normalized_url = scheme.lower()+"://"+authority.lower()

        if ( (scheme == "http" and port != 80) or (scheme == "https" and port != 443 ) ):
            normalized_url += ":" + str(port)
        normalized_url += path


    #  Build the Signature Base String
        signature_base_string = urllib.quote_plus(method)+"&"+
                                            urllib.quote_plus(normalized_url)+"&"+
                                            urllib.quote_plus(normalized_params)

    #  Build the signing key
        signing_key = urllib.quote_plus(consumer_secret)+"&"+urllib.quote_plus(access_secret)

    #  Sign
        signature_generator = hmac.new(signing_key, signature_base_string, hashlib.sha1)
        signature = urllib.quote_plus(base64.b64encode(signature_generator.digest()))

        headers_data = 'OAuth oauth_nonce=\"%s\",
                                          oauth_signature_method=\"%s\",
                                          oauth_token=\"%s\", oauth_timestamp=\"%s\",
                                          oauth_consumer_key=\"%s\", oauth_signature=\"%s\",
                                          oauth_version=\"%s\"'
                                          %(nonce,signature_method,access_token,timestamp,consumer_key,signature,oauth_version)

        url = normalized_url+"?"
        for p in url_params.keys():
            url += p+"="+params[p]+"&"
        url = url[:-1]

        req = urllib2.Request(url)
        req.add_header('Authorization', headers_data)
        req.add_header('Host', authority+":%d" %port)
        f = urllib2.urlopen(req)
        print f.read()