【Rails】【Devise】twitter・Facebookログイン実装

#機能概要
「devise」のメールアドレスの新規会員登録機能に加えて、ソーシャルログイン機能も追加します。
具体的には、

ソーシャルログインリンクをクリック
　▼　▼　▼
TwitterやFacebookに登録してあるメールアドレスを取得、パスワード入力で新規会員登録
　▼　▼　▼
ソーシャルログインリンクをクリックすると、ログインができるようになる

という流れです。

#前提
* 会員登録に使用するモデルは「User」とします。
* 「config」のgemを使用し、定数呼び出しを行える状態
* 「devise」のメールアドレスの新規会員登録機能が実装されている状態


#事前準備

* Railsインストール(下記参照)
 * [Dockerでrailsローカル開発環境構築](https://qiita.com/jung_issei/items/7999512f63863a5bddb5)
* 定数呼び出し設定(下記参照)
 * [【Rails】「config」の使い方](https://qiita.com/jung_issei/items/133a18dc1095d0be557f)
* devise設定(下記参照)
 * [【devise】メール認証のサインアップ・イン・アウト機能](https://qiita.com/jungissei/items/a3bb837cfdedda7b5d85)
* APIキーとシークレットキーを取得。
 * [Twitter Developer Platform](https://developer.twitter.com/content/developer-twitter/ja.html)
 * [Facebook for Developers
](https://developers.facebook.com/?locale=ja_JP)

## Twitter Developer Platformの設定
Twitter Developer Platformでアプリを作成後、下記の作業を行います。

**Website URL**は、「[https://example.com](https://example.com)」など、本番のものにしておきました。

**Callback URL**は、下記のようにします。
https://example.com/users/auth/twitter/callback
http://localhost/users/auth/twitter/callback
ローカル環境や本番環境など、複数設定も可能です。

**Privacy Policy URLとTerms of Service URL**に、emailを取得のため、プライバシーポリシーと、サービス利用規約ページのURLを入力しましょう。

**Permissionタブの中のAdditional permissions**は、メールアドレスの呼び出しを行うため、「Request email address」としました。

**「API key」と「API secret key」**を控えておきましょう。

## Facebook for Developersの設定
Facebook for Developersでアプリを作成後、下記の作業を行います。

**「アプリID」と「app secret」**を控えておきましょう。

#手順
## 定数でAPIキーを呼び出せるようにする。
```yml:config/settings.local.yml
twitter:
  twitter_api_key: 'API key'
  twitter_api_secret: 'API secret key'

facebook:
  facebook_api_key: 'アプリID'
  facebook_api_secret: 'app secret'
```
**再起動後**、下記のように、上記で設定した情報の呼び出しが可能になります。

```ruby
Settings.twitter[:twitter_api_key]
Settings.twitter[:twitter_api_secret]
Settings.facebook[:facebook_api_key]
Settings.facebook[:facebook_api_secret]
```


## Gemインストール

Gemfile編集

```ruby:Gemfile
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
```

Gemインストール

```terminal
$ bundle install
```

## カラム追加
「User」モデルに下記の2つのカラムを追加

* 「uid:string」
* 「provider:string」

```terminal
$ rails g migration AddColumnsToUsers uid:string provider:string
```

マイグレーション実行

```terminal
$ rails db:migrate
```


## Devise設定

```ruby:config/initializers/devise.rb
Devise.setup do |config|
  # ...

  config.omniauth :facebook,
                  Settings.facebook[:facebook_api_key],
                  Settings.facebook[:facebook_api_secret],
                  scope: 'email',
                  info_fields: 'email,name'

  config.omniauth :twitter,
                  Settings.twitter[:twitter_api_key],
                  Settings.twitter[:twitter_api_secret]

  # ...
end
```

## Userモデルにモジュールとfindメソッドを追加

* 「:omniauthable」のモジュールを追記
* findメソッドを追記

```ruby:app/models/user.rb

# ・・・

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :trackable, :confirmable,
         :omniauthable, omniauth_providers: [:facebook, :twitter]

  def self.from_omniauth(auth)
    where(uid: auth.uid).first
  end

  def self.new_with_session(_, session)
    super.tap do |user|
      if (data = session['devise.omniauth_data'])
        user.email = data['email'] if user.email.blank?
        user.provider = data['provider'] if data['provider'] && user.provider.blank?
        user.uid = data['uid'] if data['uid'] && user.uid.blank?
        user.skip_confirmation!
      end
    end
  end

# ・・・

```


## Userコントローラにコールバック処理を実装
```ruby:app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook

    # Facebook上でメール使用を許可しているかの分岐
    if request.env['omniauth.auth'].info.email.blank?
      redirect_to '/users/auth/facebook?auth_type=rerequest&scope=email'
    end

    # User.from_omniauthはModel側で実装
    user = User.from_omniauth(request.env['omniauth.auth'])

    # すでにuserが登録済みかの判定
    if user
      # 登録済みならログイン
      sign_in_and_redirect user, event: :authentication
      set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
    else
      # 新規登録用にセッションに必要情報を格納
      if (data = request.env['omniauth.auth'])
        session['devise.omniauth_data'] = {
            email: data['info']['email'],
            provider: data['provider'],
            uid: data['uid']
        }
        end
        redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end

  def twitter
    user = User.from_omniauth(request.env['omniauth.auth'])
    if user
      sign_in_and_redirect user, event: :authentication
      set_flash_message(:notice, :success, kind: "Twitter") if is_navigational_format?
    else
      if (data = request.env['omniauth.auth'])
        session['devise.omniauth_data'] = {
            email: data['info']['email'],
            provider: data['provider'],
            uid: data['uid']
        }
      end
      redirect_to new_user_registration_url
    end
  end


end
```

## 「routes.rb」に追記

```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }

  # ...
end
```

## omniauth_dataの削除
```ruby:app/controllers/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new
    session.delete('devise.omniauth_data')
    super
  end
end
```

## ログインリンク追加
```erb
<%= link_to "Facebookでログイン", user_facebook_omniauth_authorize_path %>
<%= link_to "Twitterでログイン", user_twitter_omniauth_authorize_path %>
```

#参考
【Devise+OmniAuthでユーザ認証を実装する手順 - Qiita】
https://qiita.com/kami_zh/items/94aec2d94a2b4e9a1d0b

【Devise+OmniAuthを用いてログインするためのメモ - Qiita】
https://qiita.com/minakawa-daiki/items/e54ab46b382a19dcbbf4