At previous entiry I wrote a sample how to support xAuth in Ruby Application.
In this entry, I write how to create the OAuth/xAuth Service Provider in Ruby on Rails project which uses devise plugin for authentication.
OAuth support when you use devise plugin
Installation
First of all, I’m about to write the way of supporting OAuth using OAuth plugin.
pelle/oauth-plugin – GitHub
OAuth plugin supports Rails 3 after version 0.4.0.pre1, and add Gemfile the following line.
gem "oauth-plugin", ">=0.4.0.pre1"
The installation tutorials are written in the github plugin page.
devise plugin support
In addition, to use OAuth plugin in the project which utilize devise, you need to support two methods “login_required” and “logged_in?” Devise uses such helper methods as “authenticate_#{model_name}” and “#{model_name}_signed_in?”, I wrote two helper methods.
[lib/lindoc/oauth_helpers.rb]
module Lindoc
module OauthHelpers
def self.included(recipient)
recipient.extend(ClassMethods)
recipient.class_eval do
include InstanceMethods
end
end
module InstanceMethods
def login_required
authenticate_user!
end
def logged_in?
user_signed_in?
end
end
end
end
At the controllers associated with OAuth, add this helper methods like the this.
[$RAILS_ROOT/app/oauth_clients_controller.rb]
class OauthClientsController < ApplicationController
include Lindoc::OauthHelpers
...
end
[$RAILS_ROOT/app/oauth_controller.rb]
require 'oauth/controllers/provider_controller'
class OauthController < ApplicationController
include OAuth::Controllers::ProviderController
include Lindoc::OauthHelpers
...
end
Since I want to use OAuth / xAuth authentication at API of our service, also added :oauth_required filter.
[app/controllers/api/v1/api_controller.rb]
class Api::V1::ApiController < ApplicationController
include Lindoc::OauthHelpers
before_filter :oauth_required
...
end
Rails nested parameters support
Client applications often post data in nested parameters like “foo[bar]=baz” to Rails application, but OAuth plugin doesn’t currently support this type of parameters. The reason why OAuth plugin doesn’t, is that this plugin doesn’t consider this type when it calculates a signature the way of which is written in the specification of OAuth, and answers a bad/invalid signature. So, you need to add support by overriding the method which helps calculate it.
This solution is written at Issue page of OAuth plugin.
parameter normalisation issues Nesting parameters causes problems.
[config/initializers/oauth.rb]
module OAuth
module Helper
# see https://github.com/pelle/oauth/issues#issue/8
def normalize(params)
params.sort.map do |k, values|
if values.is_a?(Array)
# multiple values were provided for a single key
values.sort.collect do |v|
[escape(k),escape(v)] * "="
end
elsif values.is_a?(Hash)
key = k
values.sort.collect do |k, v|
[escape("#{key}[#{k}]"),escape(v)] * "="
end
else
[escape(k),escape(values)] * "="
end
end * "&"
end
end
end]
xAuth support
xAuth is the authentication method which is used in twitter to support desktop/mobile applications. If you want to know how to use xAuth in your applications, you should read this document.
Using xAuth | dev.twitter.com
client application restriction
Like twitter, I want to restrict client applications which can use xAuth authentication, and I add column to decide it.
[db/migrate/xxx_create_oauth_table.rb]
class CreateOauthTables < ActiveRecord::Migration
def self.up
create_table :client_applications do |t|
...
t.boolean :xauth_enabled, :default => false
t.timestamps
end
...
end
end
/oauth/access_token signature verification method change
In OAuth 1.0 specification, service provider must verify a signature which consists of both Authentication header and GET or POST parameters called “Signature Base String”, and signed by both Consumer Secret and Access Token Secret. This verification step is implemented as filter “two_legged” or “oauth10_request_token” in OAuth plugin, OAuth::Controllers::ProviderController.
[oauth-plugin/lib/oauth/controllers/provider_controller.rb]
module OAuth
module Controllers
module ProviderController
def self.included(controller)
controller.class_eval do
...
oauthenticate :strategies => :two_legged, :interactive => false, :only => [:request_token]
oauthenticate :strategies => :oauth10_request_token, :interactive => false, :only => [:access_token]
...
The simplified difference of these two methods is to use or not to use request token. You need to change signature verification method of /oauth/access_token when you request Access Token by xAuth, because unlike OAuth clients, xAuth clients don’t have request token.
For that reason, to support xAuth, if request has “x_auth_mode=client_auth” in POST parametes which indicate client applications want to authenticate by xAuth, “two_legged” filter should be applied. I used “alias_method_chain” to seperate xAuth and OAuth authentication.
[config/initializers/oauth.rb]
module OAuth
module Controllers
module ApplicationControllerMethods
class Authenticator
def oauth10_request_token_with_xauth
if params[:x_auth_mode] == 'client_auth'
# xAuth authentication
two_legged
else
# OAuth authentication
oauth10_request_token_without_xauth
end
end
alias_method_chain :oauth10_request_token, :xauth
end
end
end
end
/oauth/access_token user verification
In /oauth/access_token request, you also verify the user using his username and password. If there is a “x_auth_mode=client_auth” POST parameter in request, verify the user and response Access Token.
[$RAILS_ROOT/app/oauth_controller.rb]
require 'oauth/controllers/provider_controller'
class OauthController < ApplicationController
include OAuth::Controllers::ProviderController
include Lindoc::OauthHelpers
....
private
def access_token_with_xauth
# To use custom failure response with devise, you need the following line.
# see https://github.com/plataformatec/devise/wiki/How-To:-Provide-a-custom-failure-response-with-Warden
warden.custom_failure!
if params[:x_auth_mode] == 'client_auth'
render_unauthorized = Proc.new do
render :nothing => true, :status => 401
end
# We support screen name and email to login
user = User.find_for_database_authentication({ :screen_name_or_email => params[:x_auth_username] })
if user &&
user.valid_password?(params[:x_auth_password]) &&
current_client_application.xauth_enabled
@token = AccessToken.where(:user_id.eq => user,
:client_application_id.eq => current_client_application,
:invalidated_at.eq => nil).limit(1).first
@token = AccessToken.create(:user => user, :client_application => current_client_application) if @token.blank?
if @token
render :text => @token.to_query
else
render_unauthorized.call
end
else
render_unauthorized.call
end
else
access_token_without_xauth
end
end
alias_method_chain :access_token, :xauth
end
References