saush

Third party user authentication with Ruby in a just few lines of code

Posted in DataMapper, Ruby, Sinatra by sausheong on April 25, 2009


Some time ago (8 years to be exact) when I was still chest-deep in Java and working in elipva, I wrote Tapestry, a centralized user management system and also an article in Javaworld describing it. That particular project is long gone (it got merged into elipva’s Zephyr product and disappeared as a standalone system) but the problems I wrote about are really quite universal.

To summarize, the main problem that Tapestry tried to solve was that software applications that serve more than 1 user at a time usually require its users to sign in. To do that, most of the time, we create a user management module. Within this user management module we need to provide these 2 major services:

As you can imagine, authentication is usually simpler than access control. Tapestry’s approach was to externalize these services into a configurable standalone system that provides both services. While I never really completed Tapestry, there are many third-party providers of these services in the market now. In this blog post I will talk about authentication and explain some ways to easily integrate your Ruby application with these third-party authentication providers.

But before that, some background. In the open era, 2 different open technologies have surfaced to solve the the issues of authentication and access control separately. OpenID is a popular digital identity used for authentication, originally developed for LiveJournal in 2005. It’s supported by many large companies including Yahoo, Google, PayPal, IBM, Microsoft and so on. As of Nov 2008, there are over 500 million OpenIDs on the Internet and about 27,000 sites have integrated it. OAuth is a newer protocol used for access control, first drafted in 2007. It allows a user to grant access to information on one site (provider) to another site (consumer) without sharing his identity. OAuth is strongly supported by Google and Yahoo, and is used also in Twitter.

At the same time, many larger organizations started to tout their proprietary own authentication and access control mechanism. Microsoft started its Passport service in 1999 and received much criticism along the way (later it changed its name to Windows Live ID). Soon after, in September 2001, the Liberty Alliance was started by Sun Microsystems, Oracle, Novell and so on (which many considered a counter to Passport) to do federated identity management. As mentioned, Google is one of the initial and strong supporter of OAuth but it also offers an older AuthSub web authentication and ClientLogin client authentication APIs while being an OpenID provider (it calls its OpenID service Federated Login). Yahoo started supporting OAuth officially in 2008, in addition to its older BBAuth APIs and is also an OpenID provider. Facebook joined the race in 2008 with Facebook Connect, which does authentication as well as share Facebook information. MySpace also came in with their platform in 2008, originally called Data Availability, which quickly became MySpaceID. In April 2009, Twitter started offering ‘Sign In with Twitter‘, which true to its name, allow users to sign in with their Twitter accounts. In addition to that Twitter uses HTTP basic authentication to protect its resources, and also recently announced support for OAuth.

As you can see there’re really lots of players in the market. RPXNow, which I blogged about previously, took advantage of these offerings and came up with a unified and simplified set of APIs to allow authentication with Yahoo, Google, Windows Live ID, Facebook, AOL, MySpace and various OpenID providers.

Looking from another perspective, there are 2 types of third party authentication APIs:

  • Web authentication for web applications
  • Client authentication for desktop or mobile client applications

The main difference between these 2 types of authentication is that web authentication can redirect the user to provider’s site for him to enter his username and password, then returns to the calling web application, usually with a success or failure token. Client authentication (usually) cannot do this, and would require the application to capture the username and password, which is sent by the client application to the provider. Obviously from a security point of view, web authentication is more secure and re-assuring to users since the user never needs to pass the username and password to the application at all.

Let’s jump into some code now. I will give 2 examples of both web authentication and client authentication each. You can find all the code here at git://github.com/sausheong/auth.gi

Let’s start with RPX because that’s the easiest. Using RPX is probably the easiest way to do third party web authentication with a large number of different providers. You can either use its provided interfaces (either embedded or pop-up Javascript) or you can call its simple API to authenticate your web application. Here’s a simple Sinatra example.

There are just 2 files. rpx_auth.html is in the public folder:

<form method="POST" action="https://chirp-saush.rpxnow.com/openid/start?token_url=http://localhost:4567/login">
	<input type="hidden" name="openid_identifier" value="yahoo.com" />
	<input type="image" src='https://a248.e.akamai.net/sec.yimg.com/i/yahoo.gif' />
</form>

This is the input form. Notice that it is entirely in HTML (it’s just a simple form submit with one post parameter). The actual work is done by auth.rb in the root folder:

%w(rubygems sinatra rest_client json).each  { |lib| require lib}

get '/login' do
  'You are logged in!' if authenticate(params[:token])
end

def authenticate(token)
  response = JSON.parse(RestClient.post 'https://rpxnow.com/api/v2/auth_info', :token => token, 'apiKey' => <rpx API KEY>, :format => 'json', :extended => 'true')
  return true if response['stat'] == 'ok'
  return false
end

You will need to register an application in RPXNow. In this example, I’m reusing Chirp-Chirp’s application in RPXNow, which is why you see the post action  is to the server chirp-saush.rpxnow.com. The token is the URL that RPX will redirect to after it authenticates the user. The pleasant surprise here is that it allows you to redirect to localhost, which eases the development effort. In this example also, I’m using Yahoo as the OpenID provider. RPXNow provides a host of other popular providers — in Chirp Chirp I actually used Yahoo, Google and Windows Live ID though it provides a lot more others. I used an image button to submit but that’s to prettify the page.

Notice that RPX redirects to the localhost with the URL ‘/login’. I created a get method for this, which in turn passes the returned token to the authenticate method. Inside this method, I used Adam Wiggin’s (of Heroku fame) really convenient rest-client library to send a post request with the token and the API key provided when you register for an application. I indicated that I want to receive the response in JSON. The response is parsed for the status and can also be parsed for data. For simplicity I didn’t return the Yahoo user information (but it’s a really small subset of data only) though I could potentially parse the JSON data for it.

And that’s it! You now have web application authentication by a third party provider, courtesy of RPX.

What if you don’t want to have a middleman like RPX to do web authentication? In this example, I was using RPX Basic, which is free because I’m using an rpxnow.com domain. If I want my own domain for authentication, I need to pay for commercial account with RPX (which is really their business model). An alternative is to use OpenID directly instead, which is very popular and doesn’t add too much complexity.

This is the input form for OpenID authentication, openid_auth.html:

<form method="get" action='/login'>
  OpenID identifier:
  <input type="text" class="openid" name="openid_identifier" size='50'/>
  <input type="submit" value="Verify" /><br />
</form>

The input requires the user to enter an OpenID identifier. If you’re not sure what an OpenID identifier is like you can check it out here. Most likely you’d already have one. Just like in the RPX case, the user is redirected to the OpenID provider, where he is requested to log in to that provider. After logging in, he will be returned to the calling application.

This is the Sinatra app that does this work, in a file called openid_auth.rb:

%w(rubygems sinatra openid openid/store/filesystem).each  { |lib| require lib}

REALM = 'http://localhost:4567'
RETURN_TO = "#{REALM}/complete"

get '/login' do
  checkid_request = openid_consumer.begin(params[:openid_identifier])
  redirect checkid_request.redirect_url(REALM, RETURN_TO) if checkid_request.send_redirect?(REALM, RETURN_TO)
  checkid_request.html_markup(REALM, RETURN_TO)
end

get '/complete' do
  response = openid_consumer.complete(params, RETURN_TO)
  return 'You are logged in!' if response.status == OpenID::Consumer::SUCCESS
  'Could not log on with your OpenID'
end

def openid_consumer
  @consumer = OpenID::Consumer.new(session, OpenID::Store::Filesystem.new('auth/store')) if @consumer.nil?
  return @consumer
end

Note that it is not that much longer than the RPX code. I used the ruby-openid gem from JanRain, which is probably the defacto OpenID Ruby library. The steps are only a bit more complicated:

  1. The input form asks the user to enter an OpenID identifier and sends it to the ‘/login’ get block (it could easily be a post block)
  2. The ‘/login’ block first gets a consumer object. To create the consumer object, you need to pass in the session where you will store the request information (this is usually provided by the web framework) as well as a store. The store is the place where we keep associations, the shared secrets between the relying party (your application) and an OpenID provider as well as nonces, cryptographic one-time alphanumeric strings used to prevent replay attacks. In the example above, I created the store in the file system.
  3. Once we have the consumer, we initiate the start of the authentication process by calling its begin method with the identifier, which returns a CheckIDRequest object. This object contains the state necessary to generate the actual OpenID request
  4. Next we ask the CheckIDRequest object if we should send the HTTP request as a redirect or if we should produce a html post form for the user
  5. Then we either redirect the user to the OpenID provider or create a HTML form that does the same thing. Here we provide a return_to path for the OpenID provider to return the user to our application with a bunch of other data
  6. Once the user authenticates himself, he will be sent back to our application, and I used another get block ‘/complete’ to process the returned data
  7. We don’t need to go through the returned parameters one by one, we can re-use the same consumer object we created earlier and use the complete method to process those parameters
  8. The complete method returns a response object, which we can check for status

The process actually looks more complicated than it really is. The ruby-openid library simplifies the processing a great deal. In the example above, I only wanted to check if the user is valid or not, if I wanted to get more data on the user, there are a bunch of other things we can do.

We’ve just covered the web authentication. Let’s move on to client authentication, which doesn’t involve a browser. Google provides ClientLogin, which is an authentication API specifically desktop or mobile clients. Using it is actually simpler than web authentication. For this example and the next, I will use Shoes, the excellent Ruby GUI toolkit by why the lucky stiff. To run the code, you need to download and install Shoes, then follow the instructions on how to execute Shoes applications. I won’t go through how to create a Shoes application here though, that’s for another day. Here’s the complete code for using Google’s ClientLogin API, in a file called goog-auth-client.rb:

Shoes.setup do
   gem 'rest-client'
   gem 'net-ssh'

end
%w(rest_client openssl.so openssl/bn openssl/cipher openssl/digest openssl/ssl openssl/x509 net/ssh).each  { |lib| require lib}

class GoogleAuthClient < Shoes
  url '/', :login
  url '/list', :list

  def login
    stack do
      title "Please login", :margin => 4
      para "Log in using your Google credentials!"
      flow :margin_left => 4 do para "Email"; @me = edit_line; end
      flow :margin_left => 4 do para "Password"; @password = edit_line :secret => true; end
      button "login" do $me = @me.text; authenticate(@me.text,@password.text) ? visit('/list') : alert("Wrong email or password"); end
    end
  end

  def list
    title "You are logged in!"
  end

  def authenticate(email,password)
    response = RestClient.post 'https://www.google.com/accounts/ClientLogin', 'accountType' => 'HOSTED_OR_GOOGLE', 'Email' => email, 'Passwd' => password, :service => 'xapi', :source => 'Goog-Auth-1.0'
    return true if response.code == 200
    return false
  end
end
Shoes.app :width => 400, :height => 200, :title => 'Google Authenticator'

You might notice that I actually included a large number of libraries. This is because there is a problem in the current Shoes build, which didn’t include the OpenSSL libraries so we’ll need to add them in manually. The method to focus on is really the authenticate method. I used the rest_client library again to send a post request to the ClientLogin URL. the parameter ’service’ indicates which Google service you want to access. In our case, we just want to authenticate a Google user, so we can use the generic service name ‘xapi’. The ’source’ parameter is a short string that identifies our application, this is for logging purposes only.

An alternative to ClientLogin and just as easy is Twitter’s basic authentication service. The code is also exactly the same. Here’s the code, in a file named twit-auth_client.rb.

class AuthClient < Shoes
  url '/', :login
  url '/list', :list

  def login
    stack do
      title "Please login", :margin => 4
      para "Log in using your Twitter credentials!"
      flow :margin_left => 4 do para "Username"; @me = edit_line; end
      flow :margin_left => 4 do para "Password"; @password = edit_line :secret => true; end
      button "login" do authenticate(@me.text,@password.text) ? visit('/list') : alert("Wrong user or password"); end
    end
  end

  def list
    title "You are logged in!"
  end

  def authenticate(user,password)
    open("http://twitter.com/account/verify_credentials.xml", :http_basic_authentication=>[user, password]) rescue return false
    return true
  end
end
Shoes.app :width => 400, :height => 200, :title => 'Blab!'

Let’s jump into the authenticate method again. I used OpenURI to send a get request to the verify credentials API. Twitter uses basic authentication to protect its resources, so we need to pass in the user and password information as well (notice that OpenURI allows you to use basic authentication). I could parse the returned results, but what happens here is that if the user and password information is invalid, it will throw an error anyway, so I just do a rescue and return false. If all is well, I can just return true.

You might notice that the code that allows me to use both Google and Twitter as a the third-party authentication provider is just a single line! Don’t you just love Ruby?

8 Responses

Subscribe to comments with RSS.

  1. Tran Duc Minh said, on April 25, 2009 at 10:54 am

    I used to try acegi for authentication + authorization :)
    It ’s a good one for service level in Java

  2. Seng Ming said, on April 28, 2009 at 10:10 pm

    Great post, I was actually considering OpenId for an app and was so glad to see that there were alternatives. Call me lazy but doing a post request a-la the google authentication is just so much easier.

    I just tried out

    response = RestClient.post 'https://www.google.com/accounts/ClientLogin', 'accountType' => 'HOSTED_OR_GOOGLE', 'Email' => email, 'Passwd' => password, :service => 'xapi', :source => 'Goog-Auth-1.0'

    and RestClient will throw a RestClient::RequestFailed exception if you submit the wrong email / password combination so you need a little more code to rescue that.

  3. Seng Ming said, on April 28, 2009 at 10:13 pm

    Great post, I was considering OpenId for an app and was so glad to see that there were alternatives. Call me lazy but doing a post request a-la the google authentication is just so much easier.

    I just tried out

    response = RestClient.post 'https://www.google.com/accounts/ClientLogin', 'accountType' => 'HOSTED_OR_GOOGLE', 'Email' => email, 'Passwd' => password, :service => 'xapi', :source => 'Goog-Auth-1.0'

    and RestClient will throw a RestClient::RequestFailed exception if you submit the wrong email / password combination so you need a little more code to rescue that.

  4. sausheong said, on April 30, 2009 at 10:14 am

    @SengMing, absolutely right, it’s just sample code. You can add a rescue at the back of the statement and then catch it somewhere else for exception handling, I guess I was just trying to make it as simple as possible.

    Google ClientLogin etc are easier of course, but you need to capture the user’s id and password and that might make your user uncomfortable, in case where you might store it for your nefarious purposes :D

  5. rubayeet said, on May 31, 2009 at 1:06 am

    Hi sausheong. Nice post! I’m trying to build a contact importer tool for my web application, and my googling returned your post. Do you happen to know if Yahoo! provides client login like Google? Twitter’s invitation tool (http://twitter.com/invitations) imports contacts from Yahoo! without the redirection. How can I achieve this?

  6. sausheong said, on May 31, 2009 at 9:10 am

    Hi Rubayeet, as far as I know Yahoo doesn’t have a client authentication mechanism for external developers. Twitter’s invitations either uses a third party provider (for contacts importing) or has direct partnership with Yahoo.

  7. Soussi said, on June 2, 2009 at 6:29 am

    Thanks for this post.
    I am currently building my GAE application and I was considering building my own authentication mechanism using Django.

    Do you think it is still worth creating one’s own authentication mechanism? May be integrating with existing providers should be enough?

  8. freebird said, on January 11, 2010 at 1:00 am

    Hi,
    Amazing guide indeed…
    I was just keen on knowing how will a similar authentication snippet would look like for Yahoo OpenID. I not able to find the exact request and response formats for the same. Kindly help me out…


Leave a Reply