Matt Kelsey

Technical notes, thoughts and examples

Authorizing and Signing a Twitter API Call Using Python

I was reading through Twitter’s excellent documenation on Authorizing and Signing an API request and decided I wanted to give implementing it a shot. The goal was to use as few external python libraries as possible. The only library I ended up using was the wonderful Requests: HTTP for Humans library to actually make the call to twitter to get my user timeline. The git repo for this is located here.

Below is an explanation of the code I wrote, in order to accomplish this. I am assuming one would know how to setup and get the needed keys for a twitter application.

The code is assuming you will have a ‘settings.cfg’ file that houses your twitter_consumer_secret, twitter_consumer_key, access_token, and access_token_secret.

It looks like:

1
2
3
4
5
[Keys]
twitter_consumer_secret: YOUR_CONSUMER_SECRET
twitter_consumer_key: YOUR_CONSUMER_KEY
access_token: YOUR_ACCESS_TOKEN
access_token_secret: YOUR_ACCESS_TOKEN_SECRET

First off, lets look at the main part of the program. The first part of the program just sets up some configuration variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
if __name__ == '__main__':

  #Read the Config File to get the twitter keys and tokens
  config = ConfigParser.RawConfigParser()
  config.read('settings.cfg')

  #method, url and parameters to call
  method = "get"
  url = "https://api.twitter.com/1.1/statuses/user_timeline.json"
  url_parameters = {
      'exclude_replies': 'true'
  }

  #configuration hash for the keys
  keys = {
      "twitter_consumer_secret": config.get(
          'Keys', 'twitter_consumer_secret'),
      "twitter_consumer_key": config.get('Keys', 'twitter_consumer_key'),
      "access_token": config.get('Keys', 'access_token'),
      "access_token_secret": config.get('Keys', 'access_token_secret')
  }

  oauth_parameters = get_oauth_parameters(
      keys['twitter_consumer_key'],
      keys['access_token']
  )

  oauth_parameters['oauth_signature'] = generate_signature(
      method,
      url,
      url_parameters, oauth_parameters,
      keys['twitter_consumer_key'],
      keys['twitter_consumer_secret'],
      keys['access_token_secret']
  )

  headers = {'Authorization': create_auth_header(oauth_parameters)}

  url += '?' + urllib.urlencode(url_parameters)

  r = requests.get(url, headers=headers)

  print json.dumps(json.loads(r.text), sort_keys=False, indent=4)

Next, lets get a collect the 6 key/value pair parameters needed for the Authorization Header and for generating the signature. These 6 parameters will be used in generating the signature, and then that signature plus these 6 parameters will be used as the Authorization Header for the request.

oauth_timestamp
Indicates when the request was created
oauth_signature_method
Cryptographic hash function used to sign the request
oauth_version
Oauth version used
oauth_token
The Access Token generated by twitter when a user authorizes a twitter app to access their account
oauth_nonce
A unique token generated per request. This helps guard against replay attacks
oauth_consumer_key
Identifies which application is making the request

I accomplish this in the get_oauth_parameters function.

1
2
3
4
5
6
7
8
9
10
11
12
def get_oauth_parameters(consumer_key, access_token):
    """Returns OAuth parameters needed for making request"""
    oauth_parameters = {
        'oauth_timestamp': str(int(time.time())),
        'oauth_signature_method': "HMAC-SHA1",
        'oauth_version': "1.0",
        'oauth_token': access_token,
        'oauth_nonce': get_nonce(),
        'oauth_consumer_key': consumer_key
    }

    return oauth_parameters

The results of this function will look something like:

1
2
3
4
5
6
7
8
{
  'oauth_nonce': 'NDIzMjg0NTQ5NDI4ODgwNDg3OTg2OTMw',
  'oauth_timestamp': '1368365251',
  'oauth_consumer_key': 'YOUR_CONSUMER_KEY',
  'oauth_signature_method': 'HMAC-SHA1',
  'oauth_version': '1.0',
  'oauth_token': 'YOUR_ACCESS_TOKEN'
}

To get the nonce, I just do this:

1
2
3
4
5
def get_nonce():
    """Unique token generated for each request"""
    n = base64.b64encode(
        ''.join([str(random.randint(0, 9)) for i in range(24)]))
    return n

After collecting the needed parameters, we can go ahead and generate the oauth_signature header value. The generate_signature method will need the following:

1
2
3
4
5
6
7
8
    oauth_parameters['oauth_signature'] = generate_signature(
        method,
        url,
        url_parameters, oauth_parameters,
        keys['twitter_consumer_key'],
        keys['twitter_consumer_secret'],
        keys['access_token_secret']
    )
method
This will either be a GET or a POST
url
The URL for which the request is directed
url_parameters
Any endpoint specific url_parameters a specific request might need
oauth_paramters
The oauth_parameters we built above
oauth_consumer_key
Your consumer key
oauth_consumer_secret
Your consumer secret
oauth_token_secret
Your access token secret
status
Optional, but if posting a tweet, this needs to be part of the signature

The method looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def generate_signature(method, url, url_parameters, oauth_parameters,
                       oauth_consumer_key, oauth_consumer_secret,
                       oauth_token_secret=None, status=None):
    """Create the signature base string"""

    #Combine parameters into one hash
    temp = collect_parameters(oauth_parameters, status, url_parameters)

    #Create string of combined url and oauth parameters
    parameter_string = stringify_parameters(temp)

    #Create your Signature Base String
    signature_base_string = (
        method.upper() + '&' +
        escape(str(url)) + '&' +
        escape(parameter_string)
    )

    #Get the signing key
    signing_key = create_signing_key(oauth_consumer_secret, oauth_token_secret)

    return calculate_signature(signing_key, signature_base_string)

The first part of this method combines the oauth_parameters, status and url_parameters into a hash, sorts the paramaters alphabetically using a OrderedDict and encodes into a single string.

According to the Twitter documentation, in order to generate the proper sting you need to:

  • Percent encode every key and value that will be signed
  • Sort the list of parameters alphabetically by encoded key
  • For each key/value pair:
    • Append the encded key to the output string
    • Append the ‘=’ character to the output string
    • Append the encoded value to the output string
    • If there are more key/value pairs remaining, append a ‘&’ character to the output string

I accomplish this in the stringify_parameters method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def stringify_parameters(parameters):
    """Orders parameters, and generates string representation of parameters"""
    output = ''
    ordered_parameters = {}
    ordered_parameters = collections.OrderedDict(sorted(parameters.items()))

    counter = 1
    for k, v in ordered_parameters.iteritems():
        output += escape(str(k)) + '=' + escape(str(v))
        if counter < len(ordered_parameters):
            output += '&'
            counter += 1

    return output

After we have our stringified parameters, we can create our signature_base_string. In order to do this we need to:

  • Conert the HTTP method to uppercase
  • Append a ‘&’ character
  • Percent Encode the URL and append it to the output string
  • Append a ‘&’ character
  • Percent encode the parameter string and append it to the output string

We are now able to create our signing key and then calculate the signature to put in the Authorization Header.

To create the signing key, we simply need to combine the oauth_consumer_secret and the oauth_token_secret joined by a ‘&’ character.

1
2
3
4
5
6
7
8
def create_signing_key(oauth_consumer_secret, oauth_token_secret=None):
    """Create key to sign request with"""
    signing_key = escape(oauth_consumer_secret) + '&'

    if oauth_token_secret is not None:
        signing_key += escape(oauth_token_secret)

    return signing_key

Now that we have the signing_key, we can take that along with the signature_base_string and calculate our SHA1 signature.

1
2
3
4
5
6
7
def calculate_signature(signing_key, signature_base_string):
    """Calculate the signature using SHA1"""
    hashed = hmac.new(signing_key, signature_base_string, hashlib.sha1)

    sig = binascii.b2a_base64(hashed.digest())[:-1]

    return escape(sig)

We have our signature, now all that is left is to create our Authorization Header for the request and make our request. In order to properly create the header, we need to combine our 6 parameters from the get_oauth_parameters with the signature we just created, order them alphabetically and append them to a string beginning with “OAuth”. The key/value parameters will need to be separated by a ‘=’, they will both need to be percent encoded and the values enclosed in ‘”’.

1
2
3
4
5
6
7
8
def create_auth_header(parameters):
    """For all collected parameters, order them and create auth header"""
    ordered_parameters = {}
    ordered_parameters = collections.OrderedDict(sorted(parameters.items()))
    auth_header = (
        '%s="%s"' % (k, v) for k, v in ordered_parameters.iteritems())
    val = "OAuth " + ', '.join(auth_header)
    return val

We should now be able to make our request:

1
2
3
4
5
6
7
    headers = {'Authorization': create_auth_header(oauth_parameters)}

    url += '?' + urllib.urlencode(url_parameters)

    r = requests.get(url, headers=headers)

    print json.dumps(json.loads(r.text), sort_keys=False, indent=4)

Feel free to email me with any questions or comments. The git repo for this is located here

Comments