devise を使用した facebook と twitter 認証について
devise を使用した facebook と twitter 認証について手順とはまった点をまとめる。
必要な gem の追加、および bundle install
gem 'omniauth-facebook' gem 'omniauth-twitter'
(devise は既にgemfileに追加されている前提)
モデルにカラムを追加
User モデルに Omniauth 用のカラムを用意する。認証には、provider と uid が必要。もし名前や画像カラムなども用意したい場合は、 name:string image:string も加える。
$ rails g migration AddOmniauthToUsers provider:string uid:string $ rails db:migrate
プロバイダと KEY の設定
連携するプロバイダとそのKEY, SECRET_KEY を宣言。KEYは直接書かないようにする。今回はこちらの記事を参考に、Encrypted Credentials に記述した。
# config/initializers/devise.rb Devise.setup do |config| config.omniauth :facebook, Rails.application.credentials.facebook[:FACEBOOK_KEY], Rails.application.credentials.facebook[:FACEBOOK_SECRET], scope: 'email', info_fields: 'email' config.omniauth :twitter, Rails.application.credentials.twitter[:TWITTER_API_KEY], Rails.application.credentials.twitter[:TWITTER_API_SECRET] end
モデルにてオムニ認証を可能にする設定
Userモデルにてオムニ認証ができるように下記を追加。下のメソッドは、先ほど追加した provider と uid カラムからユーザーを探し、見つからなかったら、ランダムなパスワードと、その他情報を auth の情報を代入して DB に追加するというメソッド。
# app/models/user.rb devise :omniauthable, omniauth_providers: %i[facebook twitter] # omniauthのコールバック時に呼ばれるメソッド def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.email = User.dummy_email(auth) user.password = Devise.friendly_token[0, 20] # user.name = auth.info.name # user.image = auth.info.image end end private def self.dummy_email(auth) "#{auth.uid}-#{auth.provider}@example.com" end
プロバイダ(今回の場合はTwitter)によってはemail情報を持たないので、パスワードとともにダミーを設定。
routes の設定
プロバイダに認証のリクエストを投げると、必要な情報を付与してプロバイダからリンクが返ってくる。それがコールバック。どのコントローラーでOmniauthコールバックを実装するかをDeviseに伝える。
# config/routes.rb devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
controller でコールバックの処理を設定
コールバックをどう処理するのかcontrollerで指示する。下では、callback_for(provider) という共通のメソッドを作って、各プロバイダに適用している。
# app/controllers/Users/omniauth_callbacks_controller.rb class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter callback_for(:twitter) end def facebook callback_for(:facebook) end # common callback method def callback_for(provider) @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format? else session["devise.#{provider}_data"] = request.env["omniauth.auth"].except("extra") redirect_to new_user_registration_url end end def failure redirect_to root_path end end
必要な View にプロバイダへのリンクを設置。
<%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path, class: "snslogin-btn-fb" %> <%= link_to "Sign in with Twitter", user_twitter_omniauth_authorize_path, class: "snslogin-btn-tw" %>
以上、devise への設定と Model, Controller, View と routes の設定ができたら連携が完了する。
はまった点1:会員登録に必要な条件を満たしているか
私の場合、devise のデフォルトで指定されている email の他に、ユーザー名、利用規約への同意をモデルにて必須にしてバリデーションをかけていた。そのためマニュアル通りだけではなく、必要な条件をすべて渡してあげる必要があった。
# app/models/user.rb devise :omniauthable, omniauth_providers: %i[facebook twitter] # omniauthのコールバック時に呼ばれるメソッド def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.email = User.dummy_email(auth) user.password = Devise.friendly_token[0, 20] <b>user.account_name = auth.info.name</b> <b>user.accepted = "true"</b> end end private def self.dummy_email(auth) "#{auth.uid}-#{auth.provider}@example.com" end
はまった点2: Twitter は email情報を request.env["omniauth.auth"] にデフォルトでは持たない。
当初、なぜか Facebook はできたのに Twitter が認証できずはまってしまった。これは、user.email = auth.info.email でプロバイダのemail情報をユーザーモデルの email に格納しようとしたが、Twitter はデフォルトで email を含んでいないため認証ができなかった。どんな情報を格納できるのか、各プロバイダの request.env["omniauth.auth"] をチェックするようにする。
やはり公式が一番。。。。
https://github.com/simi/omniauth-facebook
https://github.com/arunagw/omniauth-twitter
その他、参考にした記事
devise + omniauth で google/facebook/twitterアカウントでログイン認証を実装した際のメモ - Qiita
DeviseとOmniauthでtwitter,facebook ログイン機能 - Qiita