Twitter Authentication in Sinatra
At the recent Summit Awesome Hackathon in Manchester, my team were working on a web app that updated a user’s bio on various social networks in one place. This meant getting down and dirty with various social network APIs (not a pretty thing I can tell you). I thought I’d do a quick write up on how you can authenticate users in a Sinatra app by allowing them to sign in with Twitter.
Set Up The Site
To demonstrate this working, first of all, let’s set up a simple app that has two urls – public and private. Save the following code in a file called ‘main.rb’:
require 'sinatra'
helpers do
def admin?
true
end
end
get '/public' do
"This is the public page - everybody is welcome!"
end
get '/private' do
halt(401,'Not Authorized') unless admin?
"This is the private page - members only"
end
This sets up a helper method called admin?
that we can use to check if a user is logged in. It’s set to true by default to enable us to check that things work. We’ve also set up two route handlers. The first is for the route ‘/public’, which shows a message to everybody who visits.
The second route is a private page that will only show the message if the admin?
helper returns true. If not, then we use Sinatra’s halt
method to stop the app in it’s tracks and show a ‘Not Authorized’ message. To check that this happens, change the admin?
helper method to false, restart the server (ruby main.rb
) and go to ‘http://localhost:4567/private’.
Using Sessions to Log in and Out
We can add some route handlers to allow the user to log in and out and use sessions to keep track of whether a user is logged in or not. Change the code in main.rb to the following:
require 'sinatra'
configure do
enable :sessions
end
helpers do
def admin?
session[:admin]
end
end
get '/public' do
"This is the public page - everybody is welcome!"
end
get '/private' do
halt(401,'Not Authorized') unless admin?
"This is the private page - members only"
end
get '/login' do
session[:admin] = true
"You are now logged in"
end
get '/logout' do
session[:admin] = nil
"You are now logged out"
end
Now restart the server and try going to ‘http://localhost:4567/login’, then go to ‘http://localhost:4567/private’, and you should be able to see the page. Go to ‘http://localhost:4567/logout’, then try ‘http://localhost:4567/private’ again, and you should get the ‘unauthorized’ message.
Great – we have a working log in and log out system. The next step is to use Twitter to authenticate the user instead of just allowing anybody to log in using the login url.
Register Your App with Twitter
Before you can start using Twitter to sign in, you need to register your app with Twitter. This is easily done – just head over to https://dev.twitter.com/apps and log in using your Twitter credentials. Then click on the ‘create a new application’ button and fill in the form. You can pick any name for your application (as long as it doesn’t contain the word ‘Twitter’) and can use any website as a placeholder until you get an actual domain name for your application. For example, I used http://www.sitepoint.com.
In the callback URL field, you need to add /auth/twitter/callback
to whichever URL you used in the website field. For example, I used https://www.sitepoint.com/auth/twitter/callback. You will then be taken to a page that contains the OAuth settings for your application. Make a note of the ‘consumer key’ and ‘consumer secret’ as we will be using them in our app.
Ominauth
The library we are going to use to authenticate with is called ‘omniauth’. As the name suggests, it can actually be used to authenticate with a variety of services such as GitHub and Facebook, amongst others. It is split into different modules based on the service being used, so in this case we need to install the omniauth-twitter gem:
$ gem install omniauth-twitter
Next, you need to set up ominauth which can be done at the top of main.rb:
require 'sinatra'
require 'omniauth-twitter'
use OmniAuth::Builder do
provider :twitter, 'put your consumer key here', 'put your consumer secret here'
end
Sign in with Twitter
We need to create the sign in with Twitter functionality. First of all, we’ll edit our ‘/login’ route handler so that it redirects to the URL that has automatically been set up by Omniauth:
get '/login' do
redirect to("/auth/twitter")
end
Note that if you were using GitHub, the URL would be “/auth/github”. This will take the user into Twitter and ask them if it’s okay to allow your app to access Twitter.
If sign in is successful, it triggers a callback to ‘/auth/twitter/callback’. All we need to do now is create a route handler to deal with what we want to happen when log in is successful. This is just the same code that we used earlier in the login route handler:
get '/auth/twitter/callback' do
env['omniauth.auth'] ? session[:admin] = true : halt(401,'Not Authorized')
"You are now logged in"
end
There is also a route for handling an unsuccessful login, which we need to create a handler for:
get '/auth/failure' do
params[:message]
end
This displays the message that is sent back from Twitter as to why the authentication failed.
Now we have a fully functional login system that uses Twitter for authentication. This is one of the things I love about Sinatra – we’ve whipped up a fully functional login system that uses Twitter for authentication in around 40 lines of code!
Using the Twitter Credentials
Now that we have somebody logged in with Twitter, what can we do?
When a user logs in with Twitter, you actually gain access to a lot of information about them. This is stored in a hash called env['omniauth.auth']
. To see what this looks like, change the callback route handler to the following:
get '/auth/twitter/callback' do
session[:admin] = true
env['omniauth.auth']
end
Now try loggin in using Twitter and you will see all the information from your Twitter account that is held in the hash.
For example, you could welcome a user to your site like so:
get '/auth/twitter/callback' do
session[:admin] = true
"<h1>Hi #{env['omniauth.auth']['info']['name']}!</h1><img src='#{env['omniauth.auth']['info']['image']}'>"
end
This will give them a personalized welcome and show their Twitter profile picture. If you want to continue using these details then you could save them in the session hash, like so:
get '/auth/twitter/callback' do
session[:admin] = true
session[:username] = env['omniauth.auth']['info']['name']
"<h1>Hi #{session[:username]}!</h1>"
end
You could also use this as a way of authenticating users that you have stored in a database. Twitter provides all of its users with a unique id that can be a property of the User model. You could then look the user like so (this example uses the Mongoid syntax, but you should be able to make it work for your ORM of choice:
@user = User.where(twitter_id: env['omniauth.auth']['uid']
Updating Twitter
Once you’ve got a user authenticated with Twitter, you can theoretcially go on and start manipulating their Twitter account. What you are allowed to do depends on what you access the user grants to the application (you can select whether you want read only or read and write access for your application in the settings tab on the Twitter Developer site). Omniauth doesn’t actually let you manipulate a twitter account though. To do this you will need to install the twitter gem. There’s not enough room to go into using that now, maybe in a future post … but you might want to have a play around with it yourself in the meantime.