Ruby on Railsの認証 - OmniAuthでtwitter認証 + 簡単なRSpec

Ruby on Railstwitterへの認証・投稿するサンプルを実装してみました。
こんな感じのものです。

1.アプリを起動する
f:id:UnderSourceCode:20130414141353j:plain

2.「Sign in with Twitter」をクリックし、認証すると・・・
f:id:UnderSourceCode:20130504094826j:plain

3.認証されて戻ってくる
f:id:UnderSourceCode:20130414151755j:plain

4.ツイートを書き、「tweet」ボタンを押すと・・・
f:id:UnderSourceCode:20130414152005j:plain

5.Twitterに投稿し、「success」と表示される
f:id:UnderSourceCode:20130414152104j:plain

以前にもやったことはあるのですが、今回工夫したことは
・OmniAuthを使う
RSpecでのテストを書く
・他のアプリへ移植することを考え、なるべく少ないファイル数にする

ことです。

主にこのサイトを参考にさせて頂きました。
(メモ) Rails+OmniAuthによるTwitterログイン
OmniAuthによるTwitter認証の流れなどは、このサイト等を参考にして下さい。
(大変丁寧に書いてあるサイトが多い・・・)

以下、今回の実行環境とソースコードのポイントです。
ソースについてはGitHubに置いてあるので、詳細を知りたい場合は参考にして下さい。

◆実行環境
ruby 2.0.0p0
Rails 3.2.12

ソースコードのポイント
1.helperメソッド
twitterへ投稿するためのtwitter clientオブジェクトの生成、認証済みかの判定
認証情報のセッションへの登録を、TwitterOmniauthHelperに纏めました。

以下が、そのソースです。他アプリに移植する際にも、そのまま流用できるかと思います。
twitter_omniauth_helper.rb

module TwitterOmniauthHelper
def signed_in?
session[:oauth_token] != nil
end

def create_client
client = Twitter::Client.new(
:consumer_key => session[:consumer_key],
:consumer_secret => session[:consumer_secret],
:oauth_token => session[:oauth_token],
:oauth_token_secret => session[:oauth_token_secret]
)
return client
end

def set_auth_session(auth)
if auth
session[:consumer_key] = auth.extra.access_token.consumer.key
session[:consumer_secret] = auth.extra.access_token.consumer.secret
session[:oauth_token] = auth.credentials.token
session[:oauth_token_secret] = auth.credentials.secret
session[:username] = auth.extra.access_token.params[:screen_name]
else
session[:consumer_key] = nil
session[:consumer_secret] = nil
session[:oauth_token] = nil
session[:oauth_token_secret] = nil
session[:username] = nil
end
end
end

2.Twitterよりコールバックされるアクション
Twitterへの認証時、及びサインアウト時に、コールバックされるアクションが必要です。
それらをTwitterOmniauthControllerに纏めました。
このソースも、他アプリに移植する際に、そのまま流用できるかと思います。

twitter_omniauth_controller.rb

class TwitterOmniauthController < ApplicationController
include TwitterOmniauthHelper

def create
auth = request.env["omniauth.auth"]
set_auth_session(auth)
redirect_to root_url, :notice => "Signed in!"
end

def destroy
set_auth_session(nil)
redirect_to root_url, :notice => "Signed out!"
end
end

またコールバックのルーティングについても、routes.rbに記述します。
以下のmatch〜で始まる2行が、認証、サインアウト時のコールバックの定義です。

routes.rb

OmniauthTwitterSample::Application.routes.draw do
root :to => 'tweets#input'
get "tweets/input"
post "tweets/update"
match "/auth/:provider/callback" => "TwitterOmniauth#create"
match "/signout" => "TwitterOmniauth#destroy"
end

3.RSpecでのテスト
先に書いたTwitterOmniauthHelperについて、RSpecによるテストを用意しました。
とはいっても、モックを使った簡単な物ですが・・・。

twitter_omniauth_helper_spec.rb

require 'spec_helper'

describe TwitterOmniauthHelper do
describe "signed_in?" do
it "ログイン済と判定する" do
session[:oauth_token] = "xxx"
result = signed_in?
result.should eq(true)
end

it "ログインしていないと判定する" do
result = signed_in?
result.should eq(false)
end
end

describe "create_client" do
it "TwitterClientのインスタンスを返す" do
set_auth_session(OmniAuth.config.mock_auth[:twitter])
create_client
session[:consumer_key].should_not eq(nil)
session[:consumer_secret].should_not eq(nil)
session[:oauth_token].should_not eq(nil)
session[:oauth_token_secret].should_not eq(nil)
session[:username].should_not eq(nil)
end
end

describe "set_auth_session" do
it "認証した値を設定する" do
set_auth_session(OmniAuth.config.mock_auth[:twitter])
session[:consumer_key].should_not eq(nil)
session[:consumer_secret].should_not eq(nil)
session[:oauth_token].should_not eq(nil)
session[:oauth_token_secret].should_not eq(nil)
session[:username].should_not eq(nil)
end

it "サインアウト時、値を削除する" do
set_auth_session(nil)
session[:consumer_key].should eq(nil)
session[:consumer_secret].should eq(nil)
session[:oauth_token].should eq(nil)
session[:oauth_token_secret].should eq(nil)
session[:username].should eq(nil)
end
end
end

モックについては、spec_helper.rbに以下の定義を追加します。

spec_helper.rb

OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
:provider => "twitter",
:uid => "9999",
:info => {
:name => "ABCXYZ",
:email => "abc@abc.com"
},
:credentials => {
:token => "token",
:secret => "secret"
},
:extra => {
:access_token => {
:params => {
:screen_name => "ABCXYZ"
},
:consumer => Class.new{
def key
return "consumer_key"
end

def secret
return "consumer_secret"
end
}.new
}
}
})

以上です。