Authorising a Twitter app with Ramaze and OAuth

January 12th, 2012

Recently I've had great difficulty trying to authorise a Twitter app with Ramaze but today I finally cracked it and I'm going to share the solution in case anyone else needs to do this. I'm using twitter_oauth and the twitter gems to achieve it.

First, we setup a TwitterOAuth client with our consumer key and consumer secret. These are the keys that you're given when you register an app on Twitter.

client = TwitterOAuth::Client.new(
    :consumer_key => 'xxxxxx',
    :consumer_secret => 'xxxxxx'
)

In my scenario, I have a button that is pressed on my site that launches the authorisation process so the page 'twitter' doesn't actually exist but is where the form is posted to and then redirected to Twitter. To achieve this we do the following:

def twitter
  client = TwitterOAuth::Client.new(
        :consumer_key => 'xxxxxx',
        :consumer_secret => 'xxxxxx'
    )
    request_token = client.authentication_request_token(:oauth_callback => 'http://domain.com/auth')
    logger = Ramaze::Logger::RotatingInformer.new('./log')
    logger.info "setting request token = #{request_token.inspect}"
    session[:request_token] = request_token
    redirect request_token.authorize_url
end

I found using Ramaze's built-in logger to be extremely useful in debugging the application. I've left this example in so you have an idea for how to do it.

One of the most important parts that I took a while to discover is that we're calling authentication_request_token, not just request_token. This means that once a user authorises your app once then they won't be asked to re-authorise it every time they try to login.

We store the request_token in a session as we will need to use it in the next part of our app. It's also expecting to be redirected to a site on your domain called 'auth' so this is what we'll write next in our controller. This is known as the callback URL and is set both here and on your app's Twitter settings.

def auth
  client = TwitterOAuth::Client.new(
        :consumer_key => 'xxxxxx',
        :consumer_secret => 'xxxxxx'
    )
    access_token = client.authorize(
        session[:request_token].token,
        session[:request_token].secret,
        :oauth_verifier => request.params[:oauth_verifier]
    )
    p = access_token.params
    if client.authorized?
        Twitter.configure do |config|
            config.consumer_key = 'xxxxxx'
            config.consumer_secret = 'xxxxxx'
            config.oauth_token = p[:oauth_token]
            config.oauth_token_secret = p[:oauth_token_secret]
        end
        redirect MainController.r(:index)
    end
end

Once the user is redirected to our application from Twitter we need to exchange the request token for an access token. The twitter_oauth gem works the ins and outs of this for us and we simply supply it with the request token and request secret from before they were redirected and then an :oauth_verifier which is found in the query string of the URL so we use Ramaze's request.params to access it.

Within if client.authorized? we then configure the Twitter gem to make authorised requests on the user's behalf so we can do cool things like display their followers or friends or whatever you've setup your applications level of access to be.

auth is another page that doesn't have an explicit view made for it. I found that when it did, I was getting 401 errors meaning that the user was not authorised. One Stack Overflow answer suggested that this was because other requests on the page would mess with OAuth's process.

I hope this has been useful, let me know if you have any questions in the comments below.

Edit

I kept getting intermittent sessions with session[:request_token] sometimes returning nil and other times being populated with the right data. It turned out that when setting up my environment I had set Unicorn to have 2 workers so the sessions were being stored in one fork but not the other. Setting Unicorn to have 1 worker 'fixed' this but I'll have to look into another way to persist this data across sessions, any ideas?


About Development, and Ruby. There are 3 comments.




Comments

Yorick Peterse said:

If you want to persist session data while running multiple workers (e.g. when using Unicorn) you'll want to use an external cache such as Ramaze::Cache::MemCache. By default Ramaze caches everything in memory which is causing issues like the one you expected. Currently Ramaze offers the following external caches:

  • Ramaze::Cache::MemCache
  • Ramaze::Cache::Redis
  • Ramaze::Cache::Sequel

There are also a few file based caches but I wouldn't recommend using them as they're not the fastest ones out there.

Tim said:

Thanks Yorick!

That's some really good advice. I managed to get mine working by installing memcached, then gem install dalli; gem install kgio; and then adding

Ramaze::Cache.options.session = Ramaze::Cache::MemCache.using( :compression => true )

to my app.rb file, seems to be running fine!

Thanks once again :)

Yorick Peterse said:

No problem, if you have any further questions feel free to join #ramaze on Freenode :)