The "Tech. Arch."

Architecting Forward ::>

Transform Your Ruby Camping Web App Into An OAuth Provider

Intro

Over the last year OAuth or Open Authorization has really been gaining ground as a mechanism to open up/free your data hosted on various web sites and let you share that data across sites. The surge in interest can be attributed to a couple developments:

  1. Wider availability of OAuth libraries on a wide array of platforms
  2. Twitter declaring its intention to switch from Basic Authentication to OAuth
  3. Google announcing OAuth for GMail
  4. Other high-profile such as NetFlix providing OAuth support

Although a new 2.0 version of the OAuth specification is in development,
I wanted to have my Ruby Camping-based web applications act as OAuth providers now with 1.0a.
To make that happen I needed some kind of Camping OAuth plugin. Since none existed it was time to build one!

Click here if you want to skip straight to the source code

Synopsis of an OAuth Flow

Before we can dive in the making of the Camping OAuth plugin here is a quick overview of the basics of the OAuth functionality.

First, an OAuth provider has the responsibility to manage client applications (OAuth consumers) and associated tokens for a given user. The overall model looks like this:

Model Responsibilities
User The user account on the service provider app
ClientApplication The registration of a specific consumer app needing data from the service provider app
OauthToken Authorization tokens are exchanged between the consumer and the provider.
There are 2 types of tokens:

RequestToken The Request Token is a temporary credential token used to authenticate the Client Application (consumer app) at the provider site. This token typically includes the key and secret issued by the provider for the consumer.

Once a Request Token has been authorized, an OAuth Verifier is issued. The verifier is a verification code which will be passed back to the client application for use in subsequent requests.

AccessToken The Access Token is the authorized token the Client Application (consumer app) needs to pass to the provider every time it wants to invokes a given API on the provider site. The Access Token is issued once the Request Token has been explicitly authorized by the user logged in at the provider site.
OauthNonce A combination of a number used only once and a timestamp. The nonce is used to prevent replay attacks during during an OAuth exchange.

OAuth Provider API

In addition to having the data infrastructure in place to manage client application and tokens for users, a provider also needs to expose a specific OAuth API in the form of URL routes:

Route Usage
oauth/register Allows a user to register an OAuth consumer to access data (on its behalf) from an OAuth provider
oauth/request_token Allows the consumer app to provide the key and secret associated with the user’s registration so that an OAuth RequestToken can be provided.
oauth/authorize Allows the user to explicitly grant access to the consumer app on the provider site
callback Allows the OAuth provider to pass data (OAuth verifier) back to the consumer app
oauth/revoke Allows the user to explicitly revoke access to the consumer app on the provider site
oauth/access_token Allows the consumer app to obtain an OAuth AccessToken, based on an OAuth RequestToken and an OAuth verifier. The OAuth AccessToken can then be passed to the provider when invoking provider APIs.

And now here is the scenario illustrating models and routes together:

  1. User U registers consumer application C with provider application P
  2. User U logs in to consumer C
  3. User U invokes functionality in C requiring data (e.g. list of contacts) from provider P
  4. Consumer C asks provider P for an OAuth Request Token based on its key and secret
  5. Provider P validates that the user’s client application is valid (based on its key and secret)
  6. Provider P returns an OAuth Request Token R containing an authorization url
  7. Consumer C redirects user U to the authorization url at Provider P
  8. User U authorizes the request token R
  9. Provider P redirects user U to Consumer C and provides an OAuth verifier V
  10. Consumer C asks provider P for an OAuth Access Token based on the OAuth request token ID and the OAuth verifier V
  11. Provider P verifies the validitity of the verifier V for the token R
  12. Provider P returns an OAuth Access Token A to consumer C
  13. Consumer C invokes an API on provider P using the OAuth Access Token A
  14. Provider P verifies the validity of the OAuth access token A
  15. Provider P executes the invoked API and returns the data to Consumer C
  16. Consumer C leverages the data for the desired functionality

OAuth in Ruby

Pelle Braendgaard created an awesome implementation of OAuth for Ruby. You can find the library on GitHub here, and you can install it as a gem:

gem install oauth

The gem contains:

  1. The types of OAuth consumer tokens: RequestToken, AccessToken
  2. The signing components
  3. The communication components to request/exchange/process tokens

Ruby-OAuth is primarily used for an OAuth consumer application or site to request access to data hosted by an OAuth provider.
So how do we actually build a provider?
Well there is another gem called the oauth-plugin, which was primarily designed to easily add OAuth provider capabilities to a Rails web application. Let’s install it too.

gem install oauth-plugin
A.Rails

So assuming you have a Rails web app, you can use the oauth-plugin to generate:

  1. The database migration script to create the new tables (ClientApplication, RequestToken, AccessToken, Nonce) as well as to extend the existing user table
  2. The models you need to track access tokens for a given user at a given client application
  3. The controller needed to manage access to a given user’s data by a client application
  4. The views, forms and partials needed to register/manage access
B._why’s Camping

Although you can use the Ruby-OAuth gem to act as an OAuth consumer, there was no provider plugin like for Rails. So I stated thinking: “What would _why do?”. If the OAuth Plugin’s approach is based on generation to fit the Rails approach (with script/generate), then the Camping approach would have to be based on meta-programming. Let’s dive in the approach (or skip to the next section on how to add OAuth provider support).

Implementing An OAuth Plugin In Camping Using Metaprogramming

I decided to try to leverage as much as possible the templates and utility code from the oauth-plugin, but without duplicating the code. Pelle was also gracious to allow me to do so.

In the “Camping way of [dev] life”, adding features through metaprogramming means:

  1. including common OAuthCampingPlugin behaviors in the application’s main module:
    • Common helper methods related to user management, logging, etc.
    • Common controller filters

  2. extending modules with new class utility behaviors – such as logging
  3. including the common OAuth models in the application’s Models module with new desired instance features in other classes or in other modules – such as:

    These classes are “mixed in” by reading the oauth-plugin model templates and applying them using module_eval.

  4. extending the application’s Controllers module with new meta definitions for the standard OAuth routes:
    • /oauth/register
    • /oauth/request_token
    • /oauth/authorize
    • /oauth/revoke
    • /oauth/access_token

  5. including the OAuthCampingPlugin::OAuth and OAuthCampingPlugin::Helpers modules in each of the Camping controllers using class_eval.
  6. extending the application’s Controllers module with new meta definitions for common views/partials:
    • Application registration
    • Token authorization
    • Token revocation
    • Errors

  7. including the OAuthCampingPlugin view methods in the application’s Views module using module_eval.

Note: if you are interested in Ruby metaprogramming, check-out the reference section.

Here is a diagram illustrating the parallel between modules in your provider web app and the modules in the camping-oauth plugin:

Adding OAuth Provider Support To Your App

Here is the approach we will be taking:

# To Do
A Start with a basic Camping shell
B Customize the main module
C Plug in the OAuth models
D Create a common helpers module
E Plug in the OAuth controllers
F Plug in the OAuth common views
G Add basic login and registration capabilities
H Protect our API(s) using OAuth filters

A. Let’s start with a basic Camping shell

Create an camping-oauth-provider.rb file and let’s create a basic traditional Camping shell for an application named CampingOAuthProvider:

gem 'camping' , '>= 2.0'	
%w(rubygems active_record camping camping/session markaby json erb).each { |lib| require lib }

Camping.goes :CampingOAuthProvider

module CampingOAuthProvider
	include Camping::Session

	def CampingOAuthProvider.create
	end
end

module CampingOAuthProvider::Models
end

module CampingOAuthProvider::Helpers
end

module CampingOAuthProvider::Controllers
	class Index
		def get 
			render :index
		end
	end
end

module CampingOAuthProvider::Views
	def index
		h1 'My CampingOAuthProvider App'
		div 'To be continued ...'
	end
end

CampingOAuthProvider.create

Now we have a skeletal Camping app that we can run as follows:

camping camping-oauth-provider.rb

Access the app from the browser at the following url:


http://localhost:3001/

For our controllers we will leverage Magnus Holm’s (a.k.a. @judofyr) excellent filtering_camping gem, which provides a controller filter mechanism similar to Rails filters, but in more basic and simpler way. So let’s install it:

gem install filtering_camping

We will need to reference the four gems: filtering_camping oauth, oauth-plugin, and camping-oauth. Also we will require the various files we need: oauth, oauth/server, oauth/request_proxy, oauth/request_proxy/rack_request, camping-oauth, as well as the filtering_camping plugin.So now the top of the source should look like this:

gem 'camping' , '>= 2.0'	
gem 'filtering_camping'
gem 'oauth'
gem 'oauth-plugin'

%w(rubygems active_record camping camping/session markaby json erb
oauth
oauth/server
oauth/request_proxy
oauth/request_proxy/rack_request
filtering_camping
camping-oauth
).each { |lib| require lib }

Camping.goes :CampingOAuthProvider

B.Customizing the main module

Ok, so now we’re ready to enhance the main app module. First we’ll make sure to include the Camping::Session and CampingFilters modules, and to extend the app module with OAuthCampingPlugin, like so:

module CampingOAuthProvider
	include Camping::Session
	include CampingFilters
	extend  OAuthCampingPlugin
	
	# ...
end

This gives us the ability to leverage a logger for the camping-oauth plugin.

	OAuthCampingPlugin.logger = Logger.new(File.dirname(__FILE__) + '/CampingOAuthProvider.log');
	OAuthCampingPlugin.logger.level = Logger::DEBUG

Now let’s customize the create method by adding a call to OAuthCampingPlugin.create, so we can give the plugin to run any needed initialization.

	def CampingOAuthProvider.create
		OAuthCampingPlugin.create
	end

Ok, at this point we have a minimally configured application module. Our next step is to move on to the Models module.

C.Plugging in the OAuth models

First, we’ll include the include OAuthCampingPlugin::Models module so we can get all the OAuth-specific models. Then we’ll define a User model. The User will need to keep track of the client applications it provided access to. It will also manage the tokens associated with these applications. Our model will look like this:

class User < Base;
	has_many :client_applications
	has_many :tokens, 
		:class_name=>"OauthToken",
		:order=>"authorized_at desc",
		:include=>[:client_application]

end

Now we need a CreateUserSchema migration class to define our database tables for User, and OAuth models. In the up and down methods we will plugin a call to the corresponding method from the OAuthCampingPlugin::Models module to create the tables for ClientApplication, OAuthToken, and OauthNonce.

class CreateUserSchema < V 1.0
	def self.up
		create_table :CampingOAuthProvider_users, :force => true do |t|
			t.integer 	:id, :null => false
			t.string		:username
			t.string		:password
		end
		
		User.create :username => 'admin', :password => 'camping'
		
		OAuthCampingPlugin::Models.up
	end
	
	def self.down		
		OAuthCampingPlugin::Models.down
		drop_table :CampingOAuthProvider_users
	end
end

At this point we can go back to the main module and add the code to configure the ActiveRecord connection and invoke our new schema migration if the User table does not exist yet. This code will be added to the create method:

module CampingOAuthProvider
	# ...	

	def CampingOAuthProvider.create
		dbconfig = YAML.load(File.read('config/database.yml'))							
		Camping::Models::Base.establish_connection  dbconfig['development']		
		
		OAuthCampingPlugin.create
		
		CampingOAuthProvider::Models.create_schema :assume => (CampingOAuthProvider::Models::User.table_exists? ? 1.1 : 0.0)
	end
end

You probably noticed that the database configuration is loaded from a database.yml file. So let’s create a subfolder named config and a file named database.yml, then let’s configure the yaml file as follows:

development:
  adapter: sqlite3
  database: campingoauthprovider.db

Now if we restart the application, our migration should be executed:

D.Creating a common helpers module

The Helpers module is used in Camping to provide common utilities to both the Controllers and Views modules. Enhancing our Helpers module is very easy, we need to add both and extend and an include of the OAuthCampingPlugin::Helpers module so we can enhance both instance and class sides:

module CampingOAuthProvider::Helpers
	extend OAuthCampingPlugin::Helpers
	include OAuthCampingPlugin::Helpers
end

E.Plugging in the OAuth controllers

We will need to extend our app Controllers module with the OAuthCampingPlugin::Controllers module using the extend statement. Then just before the end of the Controllers module, we’ll add a call to the include_oauth_controllers method. This is how camping-oauth will inject and plugin the common OAuth controllers and helpers. It is important that this call always remaining the last statement of the module, even when you add new controller classes. So the module should look like so:

module CampingOAuthProvider::Controllers
	extend OAuthCampingPlugin::Controllers

	# ...

	include_oauth_controllers
end #Controllers

Before we continue fleshing out the logic of our controllers, let’s finish hooking up the Views module.

F.Plugging in the OAuth common views

We will need to extend our app Views module with the OAuthCampingPlugin::Views module using the extend statement. Then just before the end of the Views module, we’ll add a call to the include_oauth_views method. This is how camping-oauth will inject and plugin the common OAuth views. It is important that this call always remaining the last statement of the module, even when you add new view methods. So the module should look like so:

module CampingOAuthProvider::Views
	extend OAuthCampingPlugin::Views

	# ...
	
	include_oauth_views
end

G.Adding basic login and registration capabilities

Let’s add a Login controller class to our Controllers module:

	class Login < R '/login'			
		def get
			render :login
		end
		
		def post
			@user = User.find_by_username_and_password(input.username, input.password)

			if @user
				@state.user_id = @user.id

				if @state.return_to.nil?
					redirect R(Index)
				else
					return_to = @state.return_to
					@state.return_to = nil
					redirect(return_to)
				end
			else
				@info = 'Wrong username or password.'
			end
			render :login		
		end
	end

And now add the corresponding login view in the Views module"

	def login
		div @info if @info
		form :action => R(Login), :method => 'post' do
			label 'Username', :for => 'username'; br
			input :name => 'username', :type => 'text'; br

			label 'Password', :for => 'password'; br
			input :name => 'password', :type => 'text'; br

			input :type => 'submit', :name => 'login', :value => 'Login'
		end
	end

Let's verify we can login by accessing the following url:


http://localhost:3301/login

Now that login support is in place you can test out one of the OAuth controllers by navigating to the following url:


http://localhost:3301/oauth/register

Since the camping-oauth plugin installed a :before filter on the OAuthRegisterApplication controller requiring user login, you should be redirected first to the login page.
Since we created a default account when running the migration, login as admin with camping as the password. Once logged in you should be redirected back to the OAuth Application Registration page.

As a side note, you can style all common OAuth views later using CSS.
We'll let you add the SignUp controller and its signup view on your own.

H.Adding our custom API, protected by OAuth

Since the premise of this post was to make it easy for web apps to consume an OAuth-protected service, let's create a very simple controller (no view needed) to expose some data as JSON.

	class APITimeNow < R '/api/timenow'
		def get
			@result = {:now=>Time.now.utc.to_s}
			@result[:username] = @user.username if @user
			
			@headers['Content-Type'] = "application/json"
			log_debug @result.to_json
			@result.to_json
		end
	end

Now we can test it by navigating to the following url (after installing the JSONview plugin for FireFox to make it easier to see the returned JSON data):


http://localhost:3301/api/timenow


Note that at this point this controller is NOT YET protected by OAuth. For that we need to declare a before filter for the APITimeNow controller requiring to be either logged in or OAuth-authenticated. So let's add this code snippet to our main module:

module GatedCampingSite
	# ...
	
	before [:APITimeNow] do
		login_or_oauth_required
	end
	
	# ...
end	

So now if we logged out (by deleting the session cookies since we have not implemented logoff) and refreshed our browser we would be redirected to the login page.

Testing And Troubleshooting

At this stage, we have a basic Camping OAuth provider, now let's test it! The first thing is to register a new OAuth consumer named camping-oauth-consumer. We'll assume that:

  1. it is located at http://localhost:3000/ (fictitious for now)
  2. it exposes a url: http://localhost:3000/callback to accept an OAuth Access token verifier once authorized


Once you register you should see the following results page:


The key and secret will be used by our consumer as credentials when accessing our OAuth provider, so copy/paste them into a notepad.
Here is what has been stored in our SQLite database:


Note: if you are interested in Lita, an AIR-based SQLite administration tool, see here.

For our first test consumer will use IRB, so open up a session and let's define 3 variables for: url of our provider, key and secret (use your own values) of our registered consumer:

@site={:site=>"http://localhost:3301"}
@mykey="SQnIXDQyhFB5q3wfZyMY"
@mysecret="PmW02FNs7rXG97sAVXMWhFoJVZ98cnj21vv6p1ad"

Now let's require oauth and let's instantiate an OAuth consumer:

require 'oauth'
@consumer = OAuth::Consumer.new(@mykey,@mysecret,@site)

You should get an instance back:

Our next step is to request an OAuth RequestToken like so:

@request_token = @consumer.get_request_token					

You should get a request token back:

Let's see how and where we should authorize this request token:

@request_token.authorize_url					

You should get back the url to authorize the specific request token you just obtained.

So let's copy this url and let's paste it back in the browser:


http://localhost:3301/oauth/authorize?oauth_token=0Qd6g3SjWHQEM6sUTcd9

We should be prompted by the OAuth Authorization controller of our provider:

If you click on the checkbox and the Authorize button, the provider will redirect you to the callback url we defined during registration passing back the Oauth token id and and a verifier code. Since we don't have a consumer web app up and running, we will get a navigation error. Here is what the target (redirection) url looks like:


http://localhost:3000/callback?oauth_token=0Qd6g3SjWHQEM6sUTcd9&oauth_verifier=71Jt3GhiwvHlZYO9zA8c

This verifier acts as a sort of session id we need to pass to get an OAuth Access Token. So from our IRB session, let's evaluate the following statement:

@verifier = '71Jt3GhiwvHlZYO9zA8c'
@access_token = @request_token.get_access_token(:oauth_verifier=>@verifier)

You should get an access token back:

So now let's call our provider api:

@response = @access_token.get('/api/timenow')
@info = @response.body

You should get back a json object:


Yippee!!!

Recap

So this concludes our whirlwind tour of OAuth from a provider and consumer side. I am leaving the following enhancements for you to do on your own:

  1. provider logout
  2. provider user registration
  3. provider view of all registered applications for a given user
  4. provider view of all tokens for a given user's registered application
  5. consumer sample web app
  6. consumer callback route

Also if you look in the examples folder of the camping-oauth gem you will find the full source for both a provider (the one we have been working on) and a consumer app (to be run on port 3302).

So What?

  1. OAuth has become a key enabler in authorizing data sharing across web sites. OAuth also "plays nice" with identity and authentication solutions such as for example OpenID.
     
  2. Ruby and Camping make it very easy to develop web applications and web services (whether you support JSON, XML, with or without REST).
    Note: if you're interested in REST support for Camping web services check out RESTstop.
     
  3. With the camping-oauth plugin you can easily and quickly allow your services or web app to be accessed from other services.

So hopefully this post will have given you a feel for how easy it is to make a Ruby Camping-based web app act as an OAuth provider using the Camping OAuth plugin.

Happy OAuth experimentations!!!

References and Resources

OAuth
Camping

Contributors/Ruby-ists

Metaprogramming
OAuth Integrations
Tools
My Other Related Posts:

Source Code

June 7th, 2010 Posted by | OAuth, Ruby, Ruby Camping | no comments

No Comments

No comments yet.

Sorry, the comment form is closed at this time.