Ruby on Rails ~OmniAuthによるTwitter/FacebookのOAuth認証を実装（コードメモ）~事前に作成したDeviseによる認証と併用

今回は、事前に作成したDeviseによる認証機能とは別に、TwitterやFacebookアカウントで認証できるように実装していきます。

#1.gemの追加。
```
Gemfile

gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
```
```bash
$ bundle install
```

#2.UserモデルにOmniAuthで利用するカラムを追加。
```bash
$ bundle exec rails generate migration AddOmniauthColumnsToUsers provider uid name
```
```db/migrate/...
class AddOmniauthColumnsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :uid,      :string, null: false, default: ""
    add_column :users, :provider, :string, null: false, default: ""
    add_column :users, :name,     :string

    add_index :users, [:uid, :provider], unique: true
  end
end
```
```bash
$ bundle exec rake db:migrate
```
uid, provider, name の3つのカラムが追加されました。

#3.OAuth認証に必要なキー・シークレットトークンを追記。

(1)Facebook の API Key

Home – Facebook開発者 で取得。https://developers.facebook.com/
ログイン → 右上の「Register Now」から developer 登録。
Facebook Developers の右上の「+新しいアプリを作成」。
・アプリ名：任意
・名前空間：任意
・カテゴリ：その他（任意）

と入力して、App ID, App Secret を取得。
アプリをFacebookに結合する方法を選択で、Facebookでログインが可能なウェブサイトに「http://localhost:3000」を入力。

(2)Twitter の API Key

Twitter Developers で取得。https://dev.twitter.com/
ログイン → 右上 My Applications → Create a new application
・Name：任意
・Description：任意
・Website：適当なURL（http://localhost:3000 だとNG）
・Callback URL：http://127.0.0.1:3000/auth/twitter/callback
（http://localhost:3000 だとNG）

その後、Consumer key, Consumer secret を取得し、 key, secret を config/initializers/devise.rb に設定。
Settings で 「Allow this application to be used to Sign in with Twitter」にチェック。一回TwitterでOAuth認証を行うと、次回からは確認画面が省略される。

```config/initializers/devise.rb
  # API key
  if Rails.env.production?     
    config.omniauth :facebook, "App ID", "App Secret"
    config.omniauth :twitter,  "Consumer key", "Consumer secret"
  else
    config.omniauth :facebook, "App ID", "App Secret"
    config.omniauth :twitter,  "Consumer key", "Consumer secret"
  end
```
#4.Userモデルにomniauthableを追加。
```app/models/user.rb
  devise :database_authenticatable, :omniauthable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable
```

#5.OAuthのcallback 用ルーティングを追加。
```config/routes.rb
devise_for :users, :controllers => {
  :sessions      => "users/sessions",
  :registrations => "users/registrations",
  :passwords     => "users/passwords",
  :omniauth_callbacks => "users/omniauth_callbacks" 
}
```
#6.Users::OmniauthCallbacksControllerを作成。
```app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
 
    if @user.persisted?
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
 
  def twitter
    # You need to implement the method below in your model
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user)
 
    if @user.persisted?
      set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end
end
```
※deviseのコントローラー作成は以下のとおり。

```bash
$ rails g devise:controllers users
```
以下のControllerが生成される。

```
confirmations_controller.rb
omniauth_callbacks_controller.rb
passwords_controller.rb
registrations_controller.rb
sessions_controller.rb
unlocks_controller.rb
```
#7.通常サインアップ時の uid フィールド設定の実装。

```app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
 
  def build_resource(hash=nil)
    hash[:uid] = User.create_unique_string
    super
  end
 
end
```

```app/models/user.rb
class User < ActiveRecord::Base
  # ・・・
 
  # 通常サインアップ時のuid用、Twitter OAuth認証時のemail用にuuidな文字列を生成
  def self.create_unique_string
    SecureRandom.uuid
  end
 
  # ・・・
end
```

#8.OAuth 認証時の email フィールド設定の実装。
```app/models/user.rb
  devise :database_authenticatable, :omniauthable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:     auth.extra.raw_info.name,
                         provider: auth.provider,
                         uid:      auth.uid,
                         email:    auth.info.email,
                         password: Devise.friendly_token[0,20]
                        )
    end
    user
  end
 
  def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:     auth.info.nickname,
                         provider: auth.provider,
                         uid:      auth.uid,
                         email:    User.create_unique_email,
                         password: Devise.friendly_token[0,20]
                        )
    end
    user
  end
 
  # 通常サインアップ時のuid用、Twitter OAuth認証時のemail用にuuidな文字列を生成
  def self.create_unique_string
    SecureRandom.uuid
  end
 
  # twitterではemailを取得できないので、適当に一意のemailを生成
  def self.create_unique_email
    User.create_unique_string + "@example.com"
  end
```
#9ビューにOAuth認証用のログインへのリンクを表示。
```app/views/layouts/application.html.erb
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook), :class => 'navbar-link' %>
<%= link_to "Sign in with Twitter",  user_omniauth_authorize_path(:twitter),  :class => 'navbar-link' %>
```

以上で終了です。