6

I have the following implementation of a twitter client using rauth (OAuth1), based on the twitter-timeline-cli.py script in the rauth examples:

from rauth.service import OAuth1Service

class TwitterClient:

    KNOWN_USERS = {
        'user1' : ("xxx", "yyy", "2342354"), # These should be real tokens
    }

    def __init__(self):
        # Get a real consumer key & secret from https://dev.twitter.com/apps/new
        self.twitter = OAuth1Service(
            name='twitter',
            consumer_key=TWITTER_CONSUMER_KEY,
            consumer_secret=TWITTER_CONSUMER_SECRET,
            request_token_url='https://api.twitter.com/oauth/request_token',
            access_token_url='https://api.twitter.com/oauth/access_token',
            authorize_url='https://api.twitter.com/oauth/authorize',
            base_url='https://api.twitter.com/1/')

    def authorize(self):
        request_token, request_token_secret = self.twitter.get_request_token()
        authorize_url = self.twitter.get_authorize_url(request_token)
        print 'Visit this URL in your browser: ' + authorize_url
        pin = raw_input('Enter PIN from browser: ')
        return request_token, request_token_secret, pin

    def init_session(self, user):
        if user in self.KNOWN_USERS :
            request_token, request_token_secret, pin = self.KNOWN_USERS[user]
        else:
            request_token, request_token_secret, pin = self.authorize()
        session = self.twitter.get_auth_session(request_token,
                                                request_token_secret,
                                                method='POST',
                                                data={'oauth_verifier': pin})
        return session

    def list_tweets(self, user):
        session = self.init_session(user)
        params = {'include_rts': 1,  # Include retweets
                  'count': 10}       # 10 tweets

        r = session.get('statuses/home_timeline.json', params=params)

        for i, tweet in enumerate(r.json(), 1):
            handle = tweet['user']['screen_name'].encode('utf-8')
            text = tweet['text'].encode('utf-8')
            print '{0}. @{1} - {2}'.format(i, handle, text)

tc = TwitterClient()

tc.list_tweets('user1')

The idea is that, if the user is not known, he is requested to authorize the application. If, on the other hand, the user has already authorized this application, the authorization tokens (request_token, request_token_secret, pin) should be reused (normally the tokens would be in a database; for the time being, they are hard-coded in the script)

But this is not working:

Traceback (most recent call last):
  File "my-twitter-timeline-cli.py", line 56, in <module>
    tc.list_tweets('user1')
  File "my-twitter-timeline-cli.py", line 43, in list_tweets
    session = self.init_session(user)
  File "my-twitter-timeline-cli.py", line 39, in init_session
    data={'oauth_verifier': pin})
  File ".../lib/python2.7/site-packages/rauth/service.py", line 326, in get_auth_session
    **kwargs)
  File ".../lib/python2.7/site-packages/rauth/service.py", line 299, in get_access_token
    process_token_request(r, decoder, key_token, key_token_secret)
  File ".../lib/python2.7/site-packages/rauth/service.py", line 25, in process_token_request
    raise KeyError(PROCESS_TOKEN_ERROR.format(key=bad_key, raw=r.content))
KeyError: 'Decoder failed to handle oauth_token with data as returned by provider. A different decoder may be needed. Provider returned: <?xml version="1.0" encoding="UTF-8"?>\n<hash>\n  <error>Invalid / expired Token</error>\n  <request>/oauth/access_token</request>\n</hash>\n'

Is it possible to reuse OAuth1 authorization tokens?

blueFast
  • 41,341
  • 63
  • 198
  • 344

1 Answers1

17

I was misunderstanding the whole process. We do not need to save the request_token, request_token_secret and pin, but the access_token and the access_token_secret.

The process is actually:

  1. Use request_token, request_token_secret and pin to get access_token and access_token_secret
  2. Save access_token and access_token_secret (to database, or whatever)
  3. Next time, reuse access_token and access_token_secret

This is my corrected test code:

from rauth.service import OAuth1Service

class TwitterClient:

    KNOWN_USERS = { # (access_token, access_token_secret)
        'user1' : ("xxx", "yyy")
    }

    def __init__(self):
        # Get a real consumer key & secret from https://dev.twitter.com/apps/new
        self.twitter = OAuth1Service(
            name='twitter',
            consumer_key=TWITTER_CONSUMER_KEY,
            consumer_secret=TWITTER_CONSUMER_SECRET,
            request_token_url='https://api.twitter.com/oauth/request_token',
            access_token_url='https://api.twitter.com/oauth/access_token',
            authorize_url='https://api.twitter.com/oauth/authorize',
            base_url='https://api.twitter.com/1/')

    def new_session(self):
        request_token, request_token_secret = self.twitter.get_request_token()
        authorize_url = self.twitter.get_authorize_url(request_token)
        print 'Visit this URL in your browser: ' + authorize_url
        pin = raw_input('Enter PIN from browser: ')
        session = self.twitter.get_auth_session(request_token,
                                                request_token_secret,
                                                method='POST',
                                                data={'oauth_verifier': pin})
        print session.access_token, session.access_token_secret # Save this to database
        return session

    def reuse_session(self, user):
        access_token, access_token_secret = self.KNOWN_USERS[user]
        session = self.twitter.get_session((access_token, access_token_secret))
        return session

    def init_session(self, user):
        if user in self.KNOWN_USERS : session = self.reuse_session(user)
        else                        : session = self.new_session()
        return session

    def list_tweets(self, user):
        session = self.init_session(user)
        params = {'include_rts': 1,  # Include retweets
                  'count': 10}       # 10 tweets

        r = session.get('statuses/home_timeline.json', params=params)

        for i, tweet in enumerate(r.json(), 1):
            handle = tweet['user']['screen_name'].encode('utf-8')
            text = tweet['text'].encode('utf-8')
            print '{0}. @{1} - {2}'.format(i, handle, text)

tc = TwitterClient()

tc.list_tweets('user1')
blueFast
  • 41,341
  • 63
  • 198
  • 344
  • Thanks so much for this code. A question, though... what if I'm not interested in user verification, but merely using the public Stream API? How do I get around `oauth_verifier` and the whole PIN thing? – Clev3r Aug 21 '13 at 21:20
  • Ahah, had this same problem with the Quickbooks API and this worked for me! – John Jun 18 '15 at 03:02