Introduction
My first impression after having my first encounter with the OAuth protocol was: bureaucracy meets the web. It’s understandable that in order to authorize third party applications users must approve access to their own information, but if I want to access my personal information under my own application why do I need to complete all this “paperwork”?
Also, user experience suffers when you have to jump to the browser and return to your application as part of the workflow. Mobile and desktop apps need more alternatives to work around that. Twitter offers the xAuth API for desktop and mobile applications but you have to send a request with “plenty of details” and may have to wait a long time to get it.
This article describes how to use the OAuth 3-legged protocol with a headless browser like HtmlUnit to get tokens from twitter without user intervention.
The example uses HtmlUnit and Jython. If you want to use HtmlUnit under .NET I recommend looking at Using HtmlUnit on .NET for Headless Browser Automation (using IKVM). WP7 developers may also want to look at the .NET article to see if it could be applied to Silverlight.
Once you obtain the token you can keep it to use in future calls. Be aware that tokens may expire based on conditions such as time. Ethically, the automated application should ask users to either allow or deny applications access to twitter.
Prerequisites
- JRE or JDK
- Download and Install the latest Jython version. Run the .jar and install it in your preferred directory (e.g: /opt/jython).
- Download and decompress setuptools-0.6c11.tar.gz
- Go to the setuptools directory. Install the package under Jython with: sudo /opt/jython/bin/jython setup.py install
- Download and decompress python-twitter-0.8.1.tar.gz
- Look at the required dependencies for python-twitter and install them with Jython:
- http://cheeseshop.python.org/pypi/simplejson
- http://code.google.com/p/httplib2/
- http://github.com/simplegeo/python-oauth2
- You’ll need to change the file oauth2/__init__.py for Jython 2.5 compatibility:
from urlparse import parse_qs, parse_qsl
to:
try: from urlparse import parse_qsl, parse_qs except ImportError: from cgi import parse_qsl, parse_qs
- Under the python-twitter-0.8.1 directory download the HtmlUnit compiled binaries from http://sourceforge.net/projects/htmlunit/files/ (we are using HtmlUnit 2.8 for this example).
- Go to the python-twitter-0.8.1 directory and Install the python-twitter package under Jython:
- sudo /opt/jython/bin/jython setup.py install
- Create a twitter application for testing and get its key and secret.
Example
get_access_token.py
Changes
- Replace consumer_key and consumer_secret with your application key/secret.
- Add the following imports and get_pincode function:
import com.gargoylesoftware.htmlunit.WebClient as WebClient import com.gargoylesoftware.htmlunit.BrowserVersion as BrowserVersion def get_pincode(url, username, password): webclient = WebClient(BrowserVersion.FIREFOX_3_6) page = webclient.getPage(url) twitter_username_or_email = page.getByXPath("//input[@id='username_or_email']")[0] twitter_password = page.getByXPath("//input[@id='password']")[0] allow_button = page.getByXPath("//input[@id='allow']")[0] twitter_username_or_email.setValueAttribute(username) twitter_password.setValueAttribute(password) page = allow_button.click() code = page.getByXPath("//kbd/code")[0] return code.getTextContent()
- Replace:
pincode = raw_input('Pincode? ')
with:
twitter_username = None # replace it with your twitter username twitter_password = None # replace it with your twitter password print "Geting pincode" pincode = get_pincode('%s?oauth_token=%s' % (AUTHORIZATION_URL, request_token['oauth_token']), twitter_username, twitter_password) print "pincode =", pincode
run.sh
#!/bin/sh /opt/jython/jython -J-classpath "htmlunit-2.8/lib/*" get_access_token.py
Complete source code
#!/usr/bin/python2.4 # # Copyright 2007 The Python-Twitter Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys # parse_qsl moved to urlparse module in v2.6 try: from urlparse import parse_qsl except: from cgi import parse_qsl import oauth2 as oauth # HTMLUnit related code import com.gargoylesoftware.htmlunit.WebClient as WebClient import com.gargoylesoftware.htmlunit.BrowserVersion as BrowserVersion def get_pincode(url, username, password): webclient = WebClient(BrowserVersion.FIREFOX_3_6) page = webclient.getPage(url) twitter_username_or_email = page.getByXPath("//input[@id='username_or_email']")[0] twitter_password = page.getByXPath("//input[@id='password']")[0] allow_button = page.getByXPath("//input[@id='allow']")[0] twitter_username_or_email.setValueAttribute(username) #password.text = password #password.setText(password) # HtmlPasswordInput twitter_password.setValueAttribute(password) page = allow_button.click() code = page.getByXPath("//kbd/code")[0] return code.getTextContent() REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token' AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize' SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate' consumer_key = None consumer_secret = None twitter_username = None twitter_password = None if consumer_key is None or consumer_secret is None: print 'You need to edit this script and provide values for the' print 'consumer_key and also consumer_secret.' print '' print 'The values you need come from Twitter - you need to register' print 'as a developer your "application". This is needed only until' print 'Twitter finishes the idea they have of a way to allow open-source' print 'based libraries to have a token that can be used to generate a' print 'one-time use key that will allow the library to make the request' print 'on your behalf.' print '' sys.exit(1) signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() oauth_consumer = oauth.Consumer(key=consumer_key, secret=consumer_secret) oauth_client = oauth.Client(oauth_consumer) print 'Requesting temp token from Twitter' resp, content = oauth_client.request(REQUEST_TOKEN_URL, 'GET') if resp['status'] != '200': print 'Invalid respond from Twitter requesting temp token: %s' % resp['status'] else: request_token = dict(parse_qsl(content)) print '' print 'Please visit this Twitter page and retrieve the pincode to be used' print 'in the next step to obtaining an Authentication Token:' print '' print '%s?oauth_token=%s' % (AUTHORIZATION_URL, request_token['oauth_token']) print '' print "Geting pincode" pincode = get_pincode('%s?oauth_token=%s' % (AUTHORIZATION_URL, request_token['oauth_token']), twitter_username, twitter_password) print "pincode =", pincode # pincode = raw_input('Pincode? ') token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(pincode) print '' print 'Generating and signing request for an access token' print '' oauth_client = oauth.Client(oauth_consumer, token) resp, content = oauth_client.request(ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % pincode) access_token = dict(parse_qsl(content)) if resp['status'] != '200': print 'The request for a Token did not succeed: %s' % resp['status'] print access_token else: print 'Your Twitter Access Token key: %s' % access_token['oauth_token'] print ' Access Token secret: %s' % access_token['oauth_token_secret'] print ''
Conclusion
We have seen how to getOAuth tokens with a headless browser. This approach can be applied to other services such as Facebook and LinkedIn. A partial list of other services you can play with is available at: http://wiki.oauth.net/w/page/12238551/ServiceProviders
Look at our previous article Web Scraping Ajax and Javascript Sites for more information about setting up and usage HtmlUnit and Jython.
Sadly the prerequisites part requires an important extra effort to have it working quickly but once you have setup all the development environment it’s plain sailing.
Resources
- OAuth articles from Eran Hammer-Lahav
- OAuth 2.0 for Android Applications
- OAuth Will Murder Your Children
- Do Facebook Oauth 2.0 Access Tokens Expire?
- OAuth2 for iPhone and iPad applications
- Movistar BlueVia’s official API for SMS
Photo taken by mariachily
This is great! But it seems that httplib2 is not compatible with jython? When I run the get_accesstoken, I have the error: unknown encoding ‘idna’ from httplib2 library.