sifue's blog

プログラマな二児の父の日常

Heroku用Sinatraを使った「Twitterでログイン」の実装例

久しぶりのブログ更新です。仕事の方はひたすらJavaなのですが、サンデープログラミングの方はRubyJavaScriptに戻ってきました。やっぱりRubyいいです。本当にプログラミングしてて楽で速い。特に読むのも書くのも短いのがいいです。JavaIDEのお陰で書くのは速いですが、読むのが時間がかかります。


今日の実装は、最近増えてきたTwitterのアカウントで自分のWebサービスを使ってもらう、いわゆる”Sign in with Twitter”の実装例です。詳しいOAuthの仕組みは、

https://dev.twitter.com/docs/auth/sign-in-with-twitter

をご覧頂くのが良いと思います。最近これで認証するサービスが多いので、感覚で知っている方も多いのではないでしょうか。


なお、参考にさせて頂いたのは、
Sinatra と OAuth を使って Twitterタイムラインを取得してみた
http://www.machu.jp/diary/20090818.html#p01
この記事になります。ちょっと古かったので、sinatratwitterの書き方を替えたり、テンプレートをhamlに変更させてもらいました。Herokuでずっと動くようにGemfileも作ってライブラリのバージョンを固定してあります。


実装してHerokuにアップロードしたサンプルは、
Sample of sign in with Twitter
http://twitter-login.heroku.com/
となります。tiwtterでログインボタンがありますので、ログイン、認証してもらうと、認証したアカウントのタイムラインが表示されます。一応ログアウトもできます。


ソースは、
Github sifue / twitter_login
https://github.com/sifue/twitter_login
においておきましたので、基本的にはここからダウンロードして、twitter_login.rbの冒頭にある、

KEY = "your_consumer_key"
SECRET = "your_consumer_secret"

ここを、https://dev.twitter.com/appsで登録した自分のアプリケーションのものに書き換え、gitのリポジトリが設定してある状況で以下のようにHerokuにpushし、動かすことができます。

$ git push heroku master

というわけで、ぜひHeroku上でサンプルを動かしてみてください。


なお、ローカルにコピーして、ruby 1.9.2とrubygemのある環境でこのフォルダに移動して

$ gem install bundler
$ bundle install
$ ruby -rubygems twitter_login.rb

と実行しても http://localhost:4567/ で動くと思います。gem installは状況によってはsudoがいるかもしれません。


というわけでソースの解説。メインの
twitter_login.rb

require 'sinatra'
require 'haml'
require 'oauth'
require 'twitter'

set :haml, {:format => :html5 }

helpers do
  include Rack::Utils
  alias_method :h, :escape_html
end

configure do
  use Rack::Session::Cookie, :secret => Digest::SHA1.hexdigest(rand.to_s)
  KEY = "your_consumer_key"
  SECRET = "your_consumer_secret"
end

before do
  if session[:access_token]
    Twitter.configure do |config|
      config.consumer_key = KEY
      config.consumer_secret = SECRET
      config.oauth_token = session[:access_token]
      config.oauth_token_secret = session[:access_token_secret]
    end
    @twitter = Twitter::Client.new
  else
    @twitter = nil
  end
end

def base_url
  default_port = (request.scheme == "http") ? 80 : 443
  port = (request.port == default_port) ? "" : ":#{request.port.to_s}"
  "#{request.scheme}://#{request.host}#{port}"
end

def oauth_consumer
  OAuth::Consumer.new(KEY, SECRET, :site => "http://twitter.com")
end

get '/' do
  @title = 'Sample of sign in with Twitter'
  haml :index
end

get '/logout' do
  @twitter = nil
  session.clear
  redirect '/'
end

get '/request_token' do
  callback_url = "#{base_url}/access_token"
  request_token = oauth_consumer.get_request_token(:oauth_callback => callback_url)
  session[:request_token] = request_token.token
  session[:request_token_secret] = request_token.secret
  redirect request_token.authorize_url
end

get '/access_token' do
  request_token = OAuth::RequestToken.new(
    oauth_consumer, session[:request_token], session[:request_token_secret])

  @access_token = request_token.get_access_token(
    {},
    :oauth_token => params[:oauth_token],
    :oauth_verifier => params[:oauth_verifier])

  session[:access_token] = @access_token.token
  session[:access_token_secret] = @access_token.secret
  redirect '/'
end


次にindexのテンプレートの
views/index.haml

!!! XML
!!!
%html
  %head
    %meta{ 'http-equiv' => 'content', :content => 'text/html; charset=utf-8'}
  %title= @title
  %body
    %div#container
      %h1= @title
      - if not @twitter
        %a(title='OAuth Login' href='/request_token')
          %img(src='sign-in-with-twitter-l.png')
      - if @twitter
        %a(title='logout' href='/logout')Logout
        %h3= 'Login now! Show your home_timeline'
        %hr/
        - @twitter.home_timeline.each do |status|
          %p= status.user.name
          %p= status.text
          %hr/


動きとしては、ルートにアクセスしたらindex.hamlを表示。ページの中にあるログインボタンを押すと/request_tokenでアクセページを取得してリダイレクト、そのあと認証のトークン類が戻ってきたらセッションに結果を入れて再度ルートページに戻すという形です。
ルートのindex.hamltwitterのクライアントインスタンスがあるかどうかで動きを変えています。


これをJavaで書くとあっさり1000行とかいっちゃうのかなと思います。xmlだけでも結構な量になりそうです。


なお
config.ru

require './twitter_login'
run Sinatra::Application

及び
Gemfile

# Detail http://devcenter.heroku.com/articles/bundler
source 'http://rubygems.org'
gem 'sinatra' , '1.3.1'
gem 'haml' , '3.1.3'
gem 'oauth' , '0.4.5'
gem 'twitter' , '1.7.2'

これはHerokuで動かしたり、Bundlerで簡単にライブラリ環境を整えるための設定になります。


Sinatraといいhamlといい、やっぱりRubyには先進的な試みがいっぱい実装されてるなぁと今日この頃。いろいろ勉強になります。