OmniauthでTwitter認証(OAuth認証)
Twitterによる外部認証をやってみることにした。
Deviseを使っているのでOmniauthで楽にできそう。
以下のページを参考にした。
OmniAuth: Overview · plataformatec/devise Wiki · GitHub
ASCIIcasts - “Episode 235 - OmniAuth Part 1”
ASCIIcasts - “Episode 236 - OmniAuth Part 2”
Ruby - deviseでfacebook,twitter認証 - Qiita [キータ]
内容の古い部分があったりこちらの要件に合わない部分があったりしたので少し変更しながら実装した。
前提
既にDeviseでユーザー認証をしているところに外部認証を追加してゆく。
Twitter Developersにアプリケーションを登録する
まず Twitter Developers にログインし、右上のアイコンメニューから My applications を選択する。
Create a new application を選択し、各項目を入力する。後から変更できるのでサクッと登録してしまって良い。Callback URL にはコールバックされないので本番のURLにしていても良い。
アプリケーションが登録できたら Details タブの OAuth settings に表示されている Consumer key と Consumer secret をメモする。
また、 Settings タブの Application Type にある Allow this application to be used to Sign in with Twitter にチェックを入れる。これにチェックを入れていないと /oauth/authenticate の代わりに /oauth/authorize に飛ばされるため、ログインの度に毎回アプリケーションにアクセスを許可するかどうか聞かれることになる。
omniauth-twitterをインストールする
Twitterアプリケーションの登録と設定変更が完了したら後はソースコードを書くだけ。
まずはomniauth-twitterをインストールする。
# Gemfile # ... gem 'omniauth-twitter' # ...
omniauthは依存関係で自動的にインストールされるので記述するのはomniauth-twitterだけでOK。Facebook等、他の認証プロバイダを使用する場合は対応するomniauth-○○を記述する。
インストールコマンドを実行してインストールは完了。
>bundle install
Omniauthの設定
次にTwitterにアクセスするため config/initializers/devise.rb に以下の記述を追加。
config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
以降、サーバー・テスト実行時には環境変数TWITTER_KEY, TWITTER_SECRETにTwitterアプリケーション作成時にメモしたConsumer key,Consumer secretをセットしておくこと。
Userクラスに以下の記述を追加。
devise :omniauthable, :omniauth_providers => [:twitter]
Controllerの作成とRouting設定
次にControllerを作成する。Deviseには Devise::OmniauthCallbacksController というクラスが既にあるが、外部認証完了時にいろいろ処理を行うためサブクラスを作成した。
# app/controllers/mydevise/omniauth_callbacks_controller.rb class Mydevise::OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter render text: request.env["omniauth.auth"] end end
ルーティング設定も変更する。
# config/routes.rb devise_for :users, controllers: { omniauth_callbacks: "mydevise/omniauth_callbacks" }
Twitter認証のためのリンクは
<%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter) %>
で作れるけど app/views/devise/shared/_links.erb を使っていればSign in/upの画面に自動的にリンクが表示される。
ここまでできれば実際にTwitter認証をしてみると画面に認証によって得られたデータが表示されるはず。
認証データを保持するモデルを作成する
次に認証データの保存先となるモデルを作成する。Userモデルにカラムを追加してる例もあるけれど、一人のユーザーが複数の認証プロバイダを使用できるようにするためと小さく分割できるところは分割した方が管理が楽だと思ったのでRailscasts(ASCIIcasts)と同様に認証モデルを作ることにした。
>rails g model authentication provider:string uid:string user_id:integer
# app/models/authentication.rb class Authentication < ActiveRecord::Base belongs_to :user attr_accessible :provider, :uid, :user validates :provider, presence: true, uniqueness: { scope: :uid }, length: { maximum: 255 } validates :uid, presence: true, length: { maximum: 255 } validates :user, presence: true end
# app/models/user.rb class User < ActiveRecord::Base has_many :authentications, dependent: :destroy, autosave: true # ... end
仕上げ
後はOmniauthCallbacksController側で認証モデルを保存したり保存された認証モデルからユーザーモデルをロードしてログインさせたりするだけ。
# app/controllers/mydevise/omniauth_callbacks_controller.rb class Mydevise::OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter omniauth = request.env["omniauth.auth"] auth = Authentication.find_by_provider_and_uid(omniauth["provider"], omniauth["uid"]) if auth sign_in_and_redirect auth.user, :event => :authentication set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format? elsif current_user current_user.authentications.create!(provider: omniauth["provider"], uid: omniauth["uid"]) redirect_to root_url set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format? else session[:omniauth] = omniauth.except('extra') redirect_to new_user_registration_url set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format? flash[:notice] += "続けて以下の項目を入力してください。" end end end
Railscasts(ASCIIcasts)にもあるけれど注意するのは新規ユーザー を作成する場合。Twitter認証ではユーザーのメールアドレスが取得できないようなのでユーザーを作成する代わりにセッションに認証データを保持しておいてユーザーの新規登録画面にリダイレクトしている。
新規登録画面側ではセッションから認証データを取得してモデルを構築する。
# config/routes.rb devise_for :users, controllers: { omniauth_callbacks: "mydevise/omniauth_callbacks", registrations: "mydevise/registrations" }
# app/controllers/mydevise/registrations_controller.rb class Mydevise::RegistrationsController < Devise::RegistrationsController def create super session[:omniauth] = nil unless @user.new_record? end private def build_resource(*args) super if session[:omniauth] @user.apply_omniauth(session[:omniauth]) end end end
apply_omniauth、view、password_required?に関してはRailscasts(ASCIIcasts)通りなので割愛。build_resourceでのバリデーションは不要なので行わないようにした。
これでTwitter認証によるログイン、既存ユーザーのTwitter認証追加、Twitter認証からのユーザー登録ができるようになった。