OAuth

I’ve been working with Twitter’s OAuth interface. It’s pretty simple once you get the hang of it, but I found it a little fiddly at the outset. (If you get something subtly wrong It Just Doesn’t Work, and there’s no clear indication of what you got wrong.) There are a lot of good OAuth implementations out there – I found Jon Crosby’s OAuthConsumer very helpful – but I would have liked to have a minimal snippet of working code in hand when I started. Which is what I present today.

The Goal

This code fetches a Request Token from Twitter. This is only the first part of a complete OAuth client implementation, but it demonstrates the request signing process, which is (IMO) the only tricky bit.

This code is not supposed to serve as a library; it’s intended only to demonstrate what goes on behind the scenes of an OAuth client implementation.

Prelimiaries

If you want to run this code, you’ll need to supply your own “consumer key” and “consumer secret”. You get them by registering an application with Twitter (at http://twitter.com/oauth_clients). As of this writing, registration is a painless, one-screen process that can be performed by anyone with a Twitter account.

The Code

You can download the source code here. Let’s take a quick look at it.

First, we establish some globals:

host                    = 'api.twitter.com'
consumer_key            = 'Get this from http://twitter.com/oauth_clients'
consumer_secret         = 'This also comes from http://twitter.com/oauth_clients'
request_token_url       = '/oauth/request_token'
access_token_url        = '/oauth/access_token'
authorize_url           = '/oauth/authorize'

Next, we define a utility function to handle parameter encoding:

def encode(s):
    return urllib.quote(s, '~')

Python’s built-in urllib.quote function does almost the right thing by default, but we also need it to leave the ‘~’ character unescaped.

Now we can define the workhorse hash function:

def hash(key, method, url, args):
    # Prep args                                                                                                                
    args = '&'.join(k+'='+v for k,v in sorted((encode(k),encode(v)) for k,v in args))

    # Prep URL (doesn't clean redundant explcit ports or query strings)                                                        
    p = url.find('/', url.find('//')+2)
    url = url[:p].lower() + url[p:]

    # Build and hash signature base string                                                                                     
    text = '&'.join(encode(e) for e in [method.upper(), url, args])
    return base64.b64encode(hmac.new(key, text, hashlib.sha1).digest())

I took some shortcuts here. OAuth is (necessarily) picky about the format of the request URL – specifically about the way it handles ports and query strings. This stuff wasn’t relevant to my test cases (and doesn’t seem like it would come up much) so I blew it off. Caveat coder.

Here’s a quick utility to generate the OAuth Authorization header:

def make_auth_header(d):
    args = ', '.join('%s="%s"'%(encode(k), encode(v)) for k,v in d.items())
    return {'Authorization':'OAuth '+args}

And, finally, we can write a function to retrieve a Request Token:

def get_request_token():
    d = {'oauth_consumer_key':consumer_key,
         'oauth_signature_method':'HMAC-SHA1',
         'oauth_timestamp':str(int(time.time())),
         'oauth_nonce':''.join(random.SystemRandom().sample(string.lowercase+string.uppercase+string.digits, 32)),
         'oauth_version':'1.0'}
    d['oauth_signature'] = hash(encode(consumer_secret)+'&', 'GET', 'http://'+host+request_token_url, d.items())

    c = httplib.HTTPConnection(host)
    c.request('GET', request_token_url, '', make_auth_header(d))
    r = c.getresponse()
    d = r.read()
    c.close()

    # User should now proceed to http://api.twitter.com/oauth/authorize?oauth_token=...                                        
    if (r.status == 200): return d

To run the code:

  • Place it somewhat importable
  • Define consumer_key and consumer_secret
  • Fire up Python
  • Import oauth
  • Run oauth.get_request_token()
Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Python. Bookmark the permalink.

Comments are closed.