(メモ) Rails+OmniAuthによるTwitterログイン

Ruby/Rails向けの認証連携フレームワークの定番らしいOmniAuthを使って、OAuth経由でTwitterに投稿するサンプルRailsアプリを作ってみました。

参考にしたサイト:

「簡単なOmniAuth」に詳細な使い方が説明されていますが、現在のOmniAuth 1.1と合っていない箇所があるので、適宜公式ドキュメントを参照しています。

動作環境:

Twitterのアプリケーション登録

まず準備作業として、Twitterの開発者サイト (https://dev.twitter.com/) で新規のアプリケーションを登録します。開発者サイトの構成は割と良く変わるようですが、この記事を書いた時点では、ログインした状態で右上の自分のアイコンにマウスオーバー→My Applicationで行けるようです。

My applicationsから "Create a new application" を選択し、必要な情報を入力します。ここで重要なのは "Callback URL" で、TwitterのWebサイト上でユーザ認証した後に、ここに入れたURLにリダイレクト (コールバック) されることになります。今回は、元ネタのASCIIcasts/RailsCastsの記述に合わせて、http://127.0.0.1:3000/auth/twitter/callback とします。

あと、本筋ではありませんが、ここで作成したアプリケーションからTwitterにメッセージを投稿するために、Access TypeをRead onlyからRead and Writeに変える必要があります。この設定は新規アプリケーション作成時にはできなくて、一旦アプリを作成してからSettingsで変更する必要があるようです。

アプリケーションのページに表示されるOAuthのConsumer key・Consumer secretの内容を後で使います。

ライブラリを使うための準備

RailsアプリケーションからOmniAuthおよびTwitterライブラリを使うために、Gemfileへの情報追加とBundlerの実行を行います。

rails newコマンドで作成したRailsアプリ (ここではtwitter-omniauth-testとします) のルート直下にあるGemfileファイルに以下の内容を追加した後で、bundle installを実行します。

twitter-omniauth-test/Gemfileの追加内容:

....
# 以下の2行を追加
gem 'omniauth-twitter'
gem 'twitter'

アプリケーションの動作手順

ここで、OmniAuth+omniauth-twitterを使ったアプリケーションの動作手順 (ページ遷移) について整理しておきます。


  1. まず、ユーザはアプリケーションのトップ画面をブラウザで開きます。この中にある "Sign in with Twitter" のリンクをクリックすると、omniauth-twitterの制御下に移ります。このURLは /auth/twitter となります。
  2. omniauth-twitterは、OAuthのアクセストークンを取得するため、Twitterのサイトにリダイレクトします。
  3. Twitterサイトとユーザの間で認証処理を行います。すでにユーザがTwitterにログイン済みである場合には、ユーザは何もせずに4.に進むことになります。
  4. TwitterサイトからOAuthコールバックURL (Twitter開発者サイトで入力したもの) にリダイレクトします。
  5. OAuthコールバックURLに対応するコントローラの中で、認証に成功したユーザについてのセッションを作成します。具体的には、Twitterから発行されたアクセストークンの格納を行います。
  6. OAuthコールバックURLからアプリケーションのトップ画面にリダイレクトします。この状態では、ユーザはすでにログイン済みなので、"Sign in with Twitter" の代わりに "Sign out" のリンクが現れます。

この動作手順のうち、1、5、6の部分はアプリケーション内で記述する必要があり、それに加えてomniauth-twitterを使うための記述が必要になります。

OmniAuth初期化スクリプトの作成

omniauth-twitterに対してOAuthのConsumer Key/Secretをセットするための初期化スクリプトを追加します。
twitter-omniauth-test/initializers/omniauth.rb:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, CONSUMER_KEY, CONSUMER_SECRET
end

CONSUMER_KEY/SECRETには、先ほど取得したConsumer key/secretの内容を入れます。

アプリケーションのトップ画面

ここではビューの中で、ユーザのログイン状態に応じて、ログイン/ログアウト用のリンクを生成します。

コントローラの中で、ユーザがログイン状態であるかどうかを判別するためのヘルパーメソッドを定義します。今回はRailsのセッション機能をそのまま使って、セッション情報にOAuthのアクセストークンが含まれていればログイン状態と判断しています。

twitter-omniauth-test/app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  protect_from_forgery
  helper_method :signed_in?

  private
  def signed_in?
    true if session[:oauth_token]
  end 
end

session[:auth_token] の格納については後述します。

ビューでは、先ほど定義した signed_in? メソッドを用いて表示するリンクを切り替えます。
twitter-omniauth-test/app/views/layouts/application_controller.html.erb:

<body>
  <div id="container">
    <div id="user_nav">
      <% if signed_in? %>
        Welcome <%= session[:username] %>!
        <%= link_to "Sign Out", "/signout" %>
      <% else %>
        <%= link_to "Sign in with Twitter", "/auth/twitter" %>
      <% end %>
    </div>
  <%= yield %>
  </div>
</body>

ここで、未ログイン状態のユーザが "Sign in with Twitter" のリンクをクリックすると、omniauth-twitterを介してTwitterサイトにリダイレクトされることになります。

セッションの作成

Twitterサイトでの認証に成功すると、http://localhost:3000/auth/twitter (Twitter開発者サイトで指定したコールバックURL) にリダイレクトされます。
このURLをコントローラsessionsのアクションcreateで処理するために、ルーティングの設定を追加します。

twitter-omniauth-test/config/routes.rb:

TwitterOmniauthTest::Application.routes.draw do
  root :to => "tweet#input"
  get "tweet/input"
  post "tweet/update"
  match "/auth/:provider/callback" => "sessions#create"
  match "/signout" => "sessions#destroy"
end

OAuthを用いた認証に成功すると、OmniAuthにより request.env["omniauth.auth"] にアクセストークンなどの情報がセットされるので、sesssions#createではこれらの情報を取得します。参照元のRailsCastではモデルオブジェクトに情報をセットしていますが、ここでは安直にsessionに入れています。
(RailsのデフォルトではCookieに入ってクライアント側に渡ることになりますが、本来はサーバ側でDBなどに格納するのが正しいはずです)

class SessionsController < ApplicationController
  def create
    auth = request.env["omniauth.auth"]
    session[:oauth_token] = auth.credentials.token
    session[:oauth_token_secret] = auth.credentials.secret
    session[:username] = auth.extra.access_token.params[:screen_name]
    redirect_to root_url, :notice => "Signed in!"
  end

  def destroy
    session[:oauth_token] = nil
    session[:oauth_token_secret] = nil
    session[:username] = nil
    redirect_to root_url, :notice => "Signed out!"
  end
end

ツイート送信

OAuthに関する処理はこれで完了ですが、取得したアクセストークンを使ってTwitterにアクセスできることを確認します。

アプリケーションのトップ画面に以下のようなフォームを作成して、フォームに入力したメッセージをTwitterに送信します。
twitter-omniauth-test/app/views/tweet/input.html.erb:

<h1>Twitter Sample Application</h1>
<p>Enter a message:</p>
<%= form_tag({:action => "update"}, {:method => "post"}) do %>
  <%= text_field_tag(:message) %>
  <%= submit_tag("Submit") %>
<% end %>

ブラウザから見た画面はこんな感じになります。

tweet#update のコントローラでは、先ほど取得したアクセストークンを使用してツイートを投稿します。

twitter-omniauth-test/app/controllers/tweet_controller.rb:

class TweetController < ApplicationController
  def input
  end

  def update
    if signed_in?
      client = Twitter::Client.new(
        :oauth_token => session[:oauth_token],
        :oauth_token_secret => session[:oauth_token_secret]
      )
      client.update(params[:message])
      @result = :success
    else
      @result = :not_signed_in
    end
  end
end