カテゴリー:
Rails
タグ:
 Rails devise Rails4 twitter facebook Omniauth oauth

このエントリーをはてなブックマークに追加
更新日時:
2014年06月01日(日)
作成日時:
2014年06月01日(日)

前の記事 / 次の記事

omniauth単体で使う方法とか自力でoauthする方法とかじゃなくて
Rails4+Devise+Omniauthを組み合わで、twitterやfacebookで認証する方法のメモ。
(Devise自体では認証させない。)

写経元

この記事では一番上のページを一番参考にしている。

各gem

目次

  1. 目指すところ
  2. 必要なgemのインストール
  3. 各サービスの設定とidとsecretの取得
  4. 各サービスのidとsecret設定の作成
  5. Userモデルの設定
  6. OmniauthCallbacksControllerの作成
  7. OmniauthCallbacksControllerのルーティング設定
  8. Authenticationモデルの作成
  9. ビューの作成
  10. OmniauthCallbacksControllerにサインアップとログインの処理を定義する
  11. RegistrationsControllerの作成
  12. RegistrationsControllerのルーティング設定
  13. turbolinksと一緒に使う場合の注意

1.目指すところ

  • twitter と facebook のアカウントを利用してサインアップとログインができるようにする。
  • Devise自体では認証しない。

2.必要なgemのインストール

twitterやfacebook以外でも対応するgemがあれば同じようにgemを追加する。

vi Gemfile
gem 'devise'

gem 'omniauth-twitter'
gem 'omniauth-facebook'

で、インストール。

$ bundle install

ちなみにこの記事を書いた時点での環境

rails: 4.0.1
devise: 3.2.4
omniauth-twitter: 1.0.1
omniauth-facebook: 1.6.0

3.各サービスの設定とidとsecretの取得

これが一番面倒。

twitter

  1. twitter自体にログインしていなければログイン する。
  2. Developers に行く。
  3. Developersに Sign in する。
  4. 右上の自分のアカウント設定っぽいところから My applications を開く。
  5. Create New App を選択する。
  6. 必要な設定を入力する。
    Name : 好きな名前
    Description : 適当な説明、なんか書いてあればよい。
    Website : 認証時にユーザーに対して表示されるURL、omniauthの動作には関与しない。
    Callback URLhttp://127.0.0.1:3000/auth/twitter/callback (Railsのローカルの開発環境を想定。)
  7. applicationが作成されたら setting のタブを選択する。
  8. "Allow this application to be used to Sign in with Twitter" のチェックを入れる。
  9. API keys のタブを選択する
  10. API key と API secret を取得

facebook

  1. facebook自体にログインしていなければ ログイン する。
  2. Developers に行く。
  3. Developersに Log in する。
  4. アプリを管理から改めて Developers に行く。
  5. ページ上部のナビゲーションバーから App を選択して Create a New App を選択する。
  6. Display Name にサイト(アプリケーション)の名前を入力する、またカテゴリーを選択して、
    アプリケーションを作成する。
  7. Setting > Basic を設定する
    App Domains : アプリケーションのドメインを登録
    テスト環境だからそんなもんねーよって場合でも頑張って登録しないと使えない。
    localhostでは駄目だけど、hosts編集してlocalhostを参照するようにすれば通る。
    例えば devel.inchiki.jp
    Contact Email : メールアドレスを入力する(なくてもエラーにならないけど重要)
    +Add Platform を選択して WebSiteを選択する。
    Site URLにサイトのURLを入力する、
    例えばRailsのテスト環境の場合 http://devel.inchiki.jp:3000/ のようにポート番号まで指定する。
  8. Setting > Advanced を設定する
    Security > Client OAuth Login を YES にする。
    Security > Valid OAuth redirect URIs にコールバックURLを設定する。
    http://devel.inchiki.jp:3000/users/auth/facebook/callback とか。
  9. 左のメニューから Status & Review を選択し、
    Do you want to make this app and all its live features available to the general public?
    を YES にする。 この時、メールアドレスが入力されていないと YES にできない。
  10. ダッシュボードから App ID と App Secret を取得

4.各サービスのidとsecret設定の作成

各サービスのid(key)とsecretを設定する方法は色々あるっぽいけど
ここでは "config/initializers/devise.rb" に設定する方法で。

セキュリティのために環境変数に仕込んでいるだけで、
別に環境変数に仕込まなければ動かない訳ではない。

vi config/initializers/devise.rb
config.omniauth :twitter,  ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
config.omniauth :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_SECRET']

5.Userモデルの設定

Devise自体では認証させないので
database_authenticatable や token_authenticatable は使わない。
その他のオプションは必要に応じて。

class User < ActiveRecord::Base
  devise :registerable,
         :omniauthable, omniauth_providers: [:twitter, :facebook]
end

ここで認証機能を殺しているので、
SessionsControllerで生成されるURLヘルパーが使えなくなる。
なので、ルーティングを定義して new_user_session_path とかを定義しておく。

vi config/routes.rb
devise_scope :user do
  get 'sign_in',  to: 'users/sessions#new',     as: :new_user_session
  get 'sign_out', to: 'users/sessions#destroy', as: :destroy_user_session
end

6.OmniauthCallbacksControllerの作成

Devise::OmniauthCallbacksController っていうのがあるので
それを継承したコントローラーに必要な記述をする。
request.env["omniauth.auth"]に何が入ってるのかは omniauth-twitter を参照。
ここではログインさせるために最低限必要な :provider と :uid 取得する。

必ずしもSomeDirの中に入れる必要はなくて親クラスと名前が被らなければなんでもいい。
このコントローラーはとりあえずの動作確認をするためにこうしているだけで、
これは後でまた編集する。

vi app/controllers/some_dir/omniauth_callbacks_controller.rb
class SomeDir::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter
    render text: omniauth
  end

  def facebook
    render text: omniauth
  end

  private 

    def omniauth
      @omniauth ||= request.env["omniauth.auth"].slice(:provider, :uid)
    end

end

7.OmniauthCallbacksControllerのルーティング設定

この設定で親クラスを継承したコントローラーが使われるようになる。

vi config/routes.rb
devise_for :users, controllers: { 
  omniauth_callbacks: "some_dir/omniauth_callbacks" 
}

8.Authenticationモデルの作成

元々あるUserモデルにカラムを追加してもいいんだけど、
複数のサービスのアカウントで同一のアカウントにログインできるようにしたいので、
写経元サイトにあるように、Userモデルが has_many する Authentication モデルを作成する。

rails g model authentication user_id:integer provider:string uid:string

で、has_many させて belongs_to させる

class User < ActiveRecord::Base
  has_many: authentications, dependent: :destroy
end

class Authentication < ActiveRecord::Base
  belongs_to :user
end

9.ビューの作成

Deviseで user_omniauth_authorize_path(provider) というヘルパーが用意されているので、
このヘルパーを使ってユーザー登録用のビューを作成する。

自前のビューを定義する方法は devise を参照。

後述のサインアップ処理ところに書いているユーザー登録ページとしてのリダイレクト先と、
ここで編集しているユーザー登録用のビューは同じもので被っているけど、
新しいビューとルーティングを定義をするのが面倒だったのでとりあえず同じビューを使っている。
とりあえずsession[:omniauth]の有無で表示内容を切り替える。

vi users/registrations/new
%p=link_to "twitterアカウントでサインアップ", user_omniauth_authorize_path(:twitter)

ログインなら

vi users/sessions/new
%p=link_to "twitterアカウントでログイン", user_omniauth_authorize_path(:twitter)

この時点でこのリンクをクリックすれば相手のサービスから値を受け取れることが確認できるはず。

で、このリンクをクリックすると、
相手サービスの認証ページに飛ばされ、twitterに定義したCallback URLに戻って来る。
ので、ここで受け取ったデータを使ってサインアップさせたりログインしたりさせればいい。

10.OmniauthCallbacksControllerにサインアップとログインの処理を定義する

vi app/controllers/some_dir/omniauth_callbacks_controller.rb
class SomeDir::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def twitter
    redirect_to_next_page
  end

  def facebook
    redirect_to_next_page
  end

  private

    def omniauth
      @omniauth ||= request.env["omniauth.auth"].slice(:provider, :uid)
    end

    def authentication
      @authentication ||= Authentication.find_by provider: omniauth[:provider], uid: omniauth[:uid]
    end

    def redirect_to_next_page
      # 既にログインしていれば何も行わない。
      if current_user
        redirect_to root_path, notice: "もうログインしてるんだけど!"

      # 既に登録があればその belongs_to である User をログインさせる。
      elsif authentication
        sign_in authentication.user
        redirect_to root_path, notice: "ログインしたよ。"

      # まだ登録がなければユーザー登録ページへリダイレクトさせて必要な項目を入力してもらう。
      else
        session[:omniauth] = omniauth
        redirect_to new_user_registration_path, notice: "必要な項目を入力してね。"
      end
    end

11.RegistrationsControllerの作成

ログインするだけならここまでで完成だけど、
ユーザー登録をするにはもう一手間かけてRegistrationsControllerをいじる必要がある。

Devise::RegistrationsControllerというのがあるので、
これを継承したクラスをつくって記述を追加する。
OmniauthCallbacksControllerと同じで名前が被らなければなんでもいい。

vi app/controllers/some_dir/registrations_controller.rb
class SomeDir::RegistrationsController < Devise::RegistrationsController
  def create
    super
    session[:omniauth] = nil uless @user.new_record?
    # これはユーザーの登録に成功していればsession[:omniauth}は不要なので削除するという記述。
  end

  protected
    #
    # このメソッドを次のようにオーバーライドすることで
    # 保存される前の @user に authentication を持たせることができる。
    # @user が保存されれば authentication も保存される。
    #
    def build_resource(*args)
      super
       if session[:omniauth]
          omniauth = session[:omniauth]
          @user.authentications.build provider: omniauth[:provider], uid: omniauth[:uid]
       end
    end
end

修正[2014/09/27]: 下から4行目 @user.build @user.authentications.build

12.RegistrationsControllerのルーティング設定

OmniauthCallbacksControllerと同様にルーティングを定義する。

vi config/routes.rb
devise_for :users, controllers: { 
  omniauth_callbacks: "some_dir/omniauth_callbacks", 
  rgistrations:       "some_dir/registrations" 
}

13.turbolinksと一緒に使う場合の注意

将来的には解消されるんだと思うけど、
現時点だとDevise自体の認証機能を殺した状態でomniauthとturbolinksを一緒に使うと
リダイレクト時にURLが変更されない不具合がある。

Deviseではユーザー認証が必要なページに

before_filter authenticate_user!

とフィルターをかけることでログインページにリダイレクトする機能が備わっているけど、
これをそのまま使うとログインページは出てくるけどURLはリダイレクト前のままになってしまう
という現象が発生する。

で、なんでそうなるのか原因は調べてないからよく分からないんだけど、
自前でオーバーライドすればとりあえず回避できる。
この方法でオーバーライドして他に影響がないかは不明だけどリダイレクトしてるだけだし・・・。

class ApplicationController < ActionController::Base
  protected    
    def authenticate_user!
      redirect_to new_user_session_path, notice: "ログインしてね。" unless user_signed_in?
    end
end

お疲れ様でした。