Building Sinatra apps with Dropbox-SDK

When I was building my Makdown Journal app I have noticed that there were no good tutorials showing you how to use the official Dropbox-SDK Gem with the Sinatra framework. Granted, this is not necessarily an issue if you know what you are doing, but since this project was the first time I was using both the SKD and Sinatra I was a little bit shaky on how to combine the two at first. So I figured I might as well write it up here, so that the next person who decides to do this has something to work off.

The Dropbox Core API tutorial is pretty good, but it only shows you how to handle the authentication for a single user, client side application running from the console. It does not show you how to authenticate in a web based, multiuser environment. If you google for Sinatra and Dropbox you will get a few hits, such as this app and this gist. You should however note that they are both using the third party provided Dropbox Gem which is different from the Official Dropbox SDK Gem. I wanted to use the official one to avoid future inconsistencies. Not that I have anything against third party gems like this. It’s just a fact of life that services such as Dropbox like to tweak their authentication schemes, and gem authors loose interest and get tired of chasing a moving target after a while. I’ve been burned by this sort of thing in the past so I try to use “official” stuff whenever possible.

So, how do you authenticate with Dropbox? Well, if you happen to be just running a console app, it is easy. First you create session with the APP_KEY and APP_SECRET values you get when you sign up for Dropbox dev account. Then you generate a request token, send the user to the Dropbox Auth page using a specially generated URL that includes your app id, and the token request. You then request the access token which is only returned if the user successfully logged into Dropbox and authorized your app using the URL you provided. The code looks more or less like this:

require 'dropbox-sdk'
 
# Create session
session = DropboxSession.new(APP_KEY, APP_SECRET)
 
# Create request token
session.get_request_token
 
# Make the user sign in and authorize this token
authorize_url = session.get_authorize_url
puts "Please visit that web page and hit 'Allow', then hit Enter here.", authorize_url
gets
 
session.get_access_token
# Now you can upload/download files

When you use Sinatra however this won’t work. Usually what you do on the web is present the user with a log-in button, which they click, authorize your app then gain access to a protected section of your app which can then manipulate their Dropbox files. Ideally you want to write your app a bit like this:

require 'sinatra'
require 'dropbox-sdk'
 
get '/' do
    erb: index
end
 
get '/login' do
    # Authenticate with Dropbox and create a session
    redirect '/stuff'
end
 
get '/stuff' do
    # redirect to login unless authenticated
    # do actual dropbox stuff
end

This won’t necessarily work since Sinatra does not store any session data between requests. Each route defined in the example above is completely stateless and self contained. To be able to “log in” and maintain the session for your users you have to enable the cookie sessions feature of the framework using the enable :sessions keyword. This gives you an auto-magical array called session which works more or less like the $_SESSION global in PHP.

The second problem is that the Dropbox session object can’t be easily passed around between the Sinatra routes. The way to actually authenticate a Dropbox session in Sinatra is:

  1. Create Dropbox session object
  2. Get request token
  3. Serialize the session object
  4. Stuff the serialized object in the session array
  5. Redirect user to Dropbox auth-url

Or in other words, you basically do this:

enable :sessions
 
get '/login' do
    # Create dropbox session object and serialize it to the Sinatra session
    dropbox_session = DropboxSession.new(APP_KEY, APP_SECRET)
    dropbox_session.get_request_token
    session[:dropbox] = dropbox_session.serialize()
 
    # redirect user to Dropbox auth page
    redirect authorize_url = dropbox_session.get_authorize_url("http://example.com/stuff")
end

The get_authorize_url() method takes a callback URL as parameter. This is the address to which your user will be sent to upon successful authorization. For us this happens to be the /stuff address. What happens now?

  1. You deserialize the dropbox session object
  2. You get the access token
  3. You make sure session is authorized

Here is some sample code:

get '/stuff' do
    # Make sure session exists
    redirect '/login' unless session[:dropbox]
 
    # deserialize DropboxSession from Sinatra session store
    dropbox_session = DropboxSession::deserialize(session[:dropbox])
 
    # check if user authorized via the web link (has access token)
    dropbox_session.get_access_token rescue redirect '/login'
 
    # Do actual dropbox stuff
end

Now you can create a DropboxClient and do all the fun stuff like uploading and/or deleting files. Once you are done fiddling around and you want to log the user out the easiest way to do this is probably to “nil out” your session array like this:

get '/logout' do
    # destroy session data
    session[:dropbox] = nil
    redirect '/'
end

If you would like to see a real life example, you can check out my Markdown Journal code on Github. Note that it will only show you how to download and/or upload files to a designated App folder but it is probably a good place to start if you are planning to make your own Sinatra based app using Dropbox-SDK.

This entry was posted in programming and tagged . Bookmark the permalink.



One Response to Building Sinatra apps with Dropbox-SDK

  1. Your code was really helpful. Thanks for this

    Reply  |  Quote

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>