こたつとみかんとプログラミング

33才実務未経験ですがウェブエンジニアにジョブチェンジするために勉強したことをアップするためのブログです。

devise を使用した facebook と twitter 認証について

devise を使用した facebooktwitter 認証について手順とはまった点をまとめる。

やりたいこと

  1. facebook または twitter でログインできるようにする。
  2. 次に利用する際、立ち戻れるようにまとめる。

環境

必要な gem の追加、および bundle install

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

(devise は既にgemfileに追加されている前提)

モデルにカラムを追加

User モデルに Omniauth 用のカラムを用意する。認証には、provideruid が必要。もし名前や画像カラムなども用意したい場合は、 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