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:
- Wider availability of OAuth libraries on a wide array of platforms
- Twitter declaring its intention to switch from Basic Authentication to OAuth
- Google announcing OAuth for GMail
- 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!
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:
|
||||
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:
- User U registers consumer application C with provider application P
- User U logs in to consumer C
- User U invokes functionality in C requiring data (e.g. list of contacts) from provider P
- Consumer C asks provider P for an OAuth Request Token based on its key and secret
- Provider P validates that the user’s client application is valid (based on its key and secret)
- Provider P returns an OAuth Request Token R containing an authorization url
- Consumer C redirects user U to the authorization url at Provider P
- User U authorizes the request token R
- Provider P redirects user U to Consumer C and provides an OAuth verifier V
- Consumer C asks provider P for an OAuth Access Token based on the OAuth request token ID and the OAuth verifier V
- Provider P verifies the validitity of the verifier V for the token R
- Provider P returns an OAuth Access Token A to consumer C
- Consumer C invokes an API on provider P using the OAuth Access Token A
- Provider P verifies the validity of the OAuth access token A
- Provider P executes the invoked API and returns the data to Consumer C
- 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:
- The types of OAuth consumer tokens: RequestToken, AccessToken
- The signing components
- 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:
- The database migration script to create the new tables (ClientApplication, RequestToken, AccessToken, Nonce) as well as to extend the existing user table
- The models you need to track access tokens for a given user at a given client application
- The controller needed to manage access to a given user’s data by a client application
- 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:
- including common OAuthCampingPlugin behaviors in the application’s main module:
- Common helper methods related to user management, logging, etc.
- Common controller filters
- extending modules with new class utility behaviors – such as logging
- 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.
- 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
- including the OAuthCampingPlugin::OAuth and OAuthCampingPlugin::Helpers modules in each of the Camping controllers using class_eval.
- extending the application’s Controllers module with new meta definitions for common views/partials:
- Application registration
- Token authorization
- Token revocation
- Errors
- 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:
- it is located at http://localhost:3000/ (fictitious for now)
- 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:
- provider logout
- provider user registration
- provider view of all registered applications for a given user
- provider view of all tokens for a given user's registered application
- consumer sample web app
- 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?
- 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.
- 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.
- 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
- OAuth Definition on Wikipedia
- OAuth Core 1.0a
- OAuth Providers Catalog
- OAuth.net site
- OAuth Universe site
- OAuth Ruby repository on GitHub
- How to turn your rails site into an OAuth Provider
- Upcoming OAuth 2.0 Protocol
Camping
Contributors/Ruby-ists
Metaprogramming
- Include vs. Extend in Ruby (by John Nunemaker)
- [Module] Mixins (in Programming Ruby)
- Extending Objects (in Programming Ruby)
- Adding module behavior with module_eval (in Programming Ruby)
- Fun with Ruby’s instance_eval and class_eval (by Brian Morearty)
OAuth Integrations
Tools
- JSONview plugin for FireFox
- Taylor Singletary's (@episod) OAuth Dancer Tool
- Lita, a SQLite administration tool
- TCP Trace
My Other Related Posts:
- Camping light (nosql) with MongoDB
- Visualize Application Metrics on NewRelic for your Ruby Camping Application
- Running the Camping Microframework on IronRuby