I’ve got a new little hobby grails project that relies on a user’s tweets to power its data collection, so I’ve been exploring using Twitter’s oauth features to do away user accounts altogether. My little project is called “Twitter Until You’re Fitter” and the idea is that I can tweet my weight “@tuyf 93kg” each week, and the app will pick up the value and graph it over time toward my goal weight (85kg). Given my current gym workload, that shouldn’t be too far away :-)
For those of you who haven’t played with oauth, it’s one of those emerging “federated SSO” type protocols. You generate a signed request from your application and sent it off to twitter. Twitter asks the logged on user whether they wish to allow your application access their account. If they agree, twitter will send your application an oauth_verifier which you can use to logon as that user. Generating and processing all those tokens is easily handled by twitter4j
In order to get started, you first have to register your application with twitter, after which you’ll be issued with a “consumer key” and a “consumer secret”. You’ll want to squirrel those away in your /grails-app/conf/Config.groovy to get access to them later:
twitter { oauth.consumer_key = 'd8sfsv9s0afs89v0sdesvs0' oauth.consumer_secret = 'lfsdDKSFjepfskvespfseruDFESilsfesfiseLFS==' }
Next up we’ll taked advantage of Grails 1.2’s new dependency management gear, by adding twitter4j to our /grails-app/conf/BuildConfig.groovy
dependencies { build 'net.homeip.yusuke:twitter4j:2.0.10' }
With our dependency in place, and our keys all set aside in config, it’s time to generate a request to send the user off to twitter. I’ve setup a little TwitterService class to abstract some of the details:
class TwitterService { def generateRequestToken(twitter, callbackUrl) { def consumerKey = ConfigurationHolder.config.twitter.oauth.consumer_key def consumerSecret = ConfigurationHolder.config.twitter.oauth.consumer_secret twitter.setOAuthConsumer(consumerKey, consumerSecret) def requestToken = twitter.getOAuthRequestToken(callbackUrl) return requestToken } }
I’m passing in the twitter4j object itself, since I’m planning on binding that object to the user’s session, and using it to access their timeline. Now we just need a controller to generate the “redirect to login” feature of my application. Something like this should do it:
def requestLogin = { def twitterClient = new twitter4j.Twitter() def returnUrl = g.createLink(controller: 'oauth', action: 'processLogin', absolute:true).toString() log.debug "Generating request with return url of [${returnUrl}]" def requestToken = twitterService.generateRequestToken(twitterClient, returnUrl) session.twitter = twitterClient session.requestToken = requestToken redirect(url:requestToken.getAuthorizationURL()) }
First we generate a link for our return url after authentication, then I call the TwitterService to generate my request token. The RequestToken class exposes a getAuthorizationURL()
which returns the URL you need to send them off to. The user will be shown an authorisation screen like this:
Assuming they click “Allow”, twitter will redirect back to your requested URL with a URL containing a oauth_verifier parameter. You can use that parameter to login to twitter as that user. Here’s the action that processes the return URL from twitter:
def processLogin = { log.debug "Processing Login Return from Twitter" if (!session.requestToken) { redirect(action: 'requestLogin') } else { def accessToken = session.twitter.getOAuthAccessToken(session.requestToken, params.oauth_verifier) log.debug "Attempting validate..." def twitterUser = session.twitter.verifyCredentials() log.debug "Validate successful for ${twitterUser.screenName}" session.user = twitterUser redirect(action: 'displayDetails') } }
That accessToken
object turns out to be really important. If you squirrel away it’s token
and tokenSecret
properties, you can login as the user any time you want with the redirect step (eg. a future session, cookie value, etc).
Phew! We’ve come a long way! We’re now actually logged on as the user, now we just need a gsp to display some timeline details. Perhaps something like this:
<html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Welcome ${session.user.name}</title> </head> <body> <h1>Successful Login for ${session.user.name}</h1> <p>Your user handle is <a href="${session.user.getURL()}">@${session.user.screenName}</a></p> <p>Your icon is: <img src="${session.user.profileImageURL}"></p> <p>Your timeline:</p> <ul> <g:each in="${session.twitter.homeTimeline}" var="status" status='i'> <li class="${ (i % 2) ? 'odd' : 'even'}"> <img src="${status.user.profileImageURL}"/> ${status.user.screenName} : ${status.text} </li> </g:each> </ul> </body> </html>
Which should give us an output like this:
And our application is fully linked up! If the user has a look in their Twitter settings, they’ll now see that our app has read-only access to their account. They can revoke that at any time (so we’ll need some error checking to handle that down the track).
Once I’ve got a bit more of the app running, I’ll get some source onto a public repo. Till then, I need to get ready for my Spin class..