bor.borygmus

A programming weblog by Hao Lian. • A long walk through an angry forest. • A series of memory leaks brought on by senility.

Let’s say, one day, you become insane and want to authorize your application to access a user’s data on Twitter. WITH OAUTH!

Twist!

A brief recap: OAuth authenticates an application. It ensures that a sequence of HTTP requests belong to exactly one person, which is hugely important if you’re providing a service API like Twitter is. Here we’ll present the OAuth with signed callback URLs, which is how Twitter provides OAuth for desktop applications—applications that might not have web browsers but nevertheless need to get the Special OAuth Pat on the Back and let the user allow it to access his data. And to do so, you do this:

You’ll grab a request token. With it, you’ll ask the user to log into Twitter and get a special PIN. This PIN is pasted into your application. You then trade your request token for an access token, with which you can perform unspeakable evil.

A first attempt at precisely this:

HTTP GET /oauth/request_token
RECEIVE "oauth_token" (request token)
OPEN BROWSER TO
    /oauth/authorize?oauth_token=[...]
RECEIVE "oauth_verifier" (PIN)
HTTP GET /oauth/access_token?
    oauth_token=[...]
    oauth_verifier=[PIN]
RECEIVE "oauth_token" (access token)

“RECEIVE” really means to parse the body of the HTTP response, which will be a key-value URL like=this&or=this, into a dictionary containing the key or keys we’re interested in. And when I say “/blah”, I really mean “http://twitter.com/blah”.

We can describe this in Official OAuth Specification Parlance, as detailed in the OAuth wiki. Remember: We are the consumer; Twitter is the service provider. Here we go:

  • Consumer requests request token.
  • Service provider issues unauthorized request token.
  • Consumer directs user to service provider (browser, PIN, et cetera).
  • Service provider authenticates user and redirects user back to consumer.
  • Consumer obtains access token.
  • Service provider grants access token.

Done. Let’s get lunch. Wait!

W—wait. Not that simple. First, /oauth/request_token actually looks like

HTTP GET /oauth/request_token?
    oauth_consumer_key=FANCY&
        oauth_version=1.0&
        oauth_timestamp=1263867975&
        oauth_nonce=HqhX2N&
        oauth_signature_method=HMAC-SHA1&
        oauth_signature=vV[snip]&
        oauth_token=&
        oauth_verifier=&

A little unpacking is needed here to explain where everything came from:

  • The consumer key identifies you with the API provider. This is given to you when you sign up with Twitter’s OAuth service.
  • The nonce and timestamp are explained in the specification.
  • The signature is computed to avoid message tampering. Here we used HMAC-SHA1, which is also noted. (HIDDEN MAGIC TREASURE: The signature also uses the consumer secret Twitter gave you back when you signed up, in case you were wondering where that went into the general tango of events. Later, once you obtain it, the signature also also uses the token secret.)
  • The token and verifier are left blank since we don’t know them yet.

And that browser URL isn’t as simple either:

OPEN BROWSER TO /oauth/authorize?
    oauth_token=22J6x[snip]&
        oauth_token_secret=FEJFd[snip]&
        oauth_callback_confirmed=true

The token secret is fortunately provided by /oauth/request_token. The callback confirmed tells Twitter that this is a desktop application and that we need the user to have a PIN to paste into us.

Finally,

/oauth/access_token?
    oauth_consumer_key=FANCY&
        oauth_version=1.0&
        oauth_timestamp=1263868017&
        oauth_nonce=9sQQC8&
        oauth_signature_method=HMAC-SHA1&
        oauth_signature=9p[snip]&
        oauth_token=22J6x[snip]&
        oauth_verifier=1010101

Same tango as above, except now we have a request token (goes into oauthtoken) and a PIN (goes into oauthverifier). Twitter now spits out an oauthtoken (which is your access token), oauthtoken_secret (which is your access secret), a Twitter user name, and a Twitter user ID.

On Twitter, your access tokens never expire. So store them somewhere, for heaven’s sake.

All together, now.

HTTP GET /oauth/request_token?
    oauth_consumer_key=FANCY&
        oauth_version=1.0&
        oauth_timestamp=1263867975&
        oauth_nonce=HqhX2N&
        oauth_signature_method=HMAC-SHA1&
        oauth_signature=vV[snip]&
        oauth_token=&
        oauth_verifier=&

HTTP response: oauth_token=22J6x[snip]&
    oauth_token_secret=FEJFd[snip]

OPEN BROWSER TO /oauth/authorize?
    oauth_token=22J6x[snip]&
        oauth_token_secret=FEJFd[snip]&
        oauth_callback_confirmed=true

User pastes PIN e.g. 1010101

/oauth/access_token?
    oauth_consumer_key=FANCY&
        oauth_version=1.0&
        oauth_timestamp=1263868017&
        oauth_nonce=9sQQC8&
        oauth_signature_method=HMAC-SHA1&
        oauth_signature=9p[snip]&
        oauth_token=22J6x[snip]&
        oauth_verifier=1010101

HTTP response: oauth_token=[access token]&
    oauth_token_secret=[access secret]&
    user_name=[user name]&
    user_id=[user ID]

An enthusiastic appendix about computing signatures, HMAC, Thomas Ptacek, and the nature of light, you know, when you really get right down to it.

Signatures are cryptography-land denizens, and boy are they fun. They are fully described in Section 9 of the specification. For OAuth, you can see how signatures are computed in the “get_signed_body” method (Python) in tav’s Google App Engine Twitter OAuth More Proper Nouns Handler.

We’ve used HMAC, which has several interesting properties. One: It’s leagues more secure than a simple SHA-# or MD# checksum. Two: Everybody confuses message authentication codes with encryption. Three: Thomas Ptacek writes handsomely about cryptography. So why is HMAC used here? Twitter needs to authenticate you to make sure you and only you are using your OAuth tokens granted. As Ptacek points out, authentication isn’t encryption—but it is HMAC.

i might have a cryptography crush on thomas ptacek

[(January 21, 2010) .]

Abandon your ideas.

Use Markdown+, but not HTML. In code blocks, beware angle brackets.