RailsでFacebookとGoogleのOAuth連携。SNS認証の方法

#はじめに
Railsアプリの実装でFacebookとGoogleでのSNS認証を実装したのでその方法を備忘録としてここに記します:head_bandage::head_bandage:
最近ではSNS認証ができないアプリケーションのが珍しい？くらいになっています。Railsでは"devise"と"omniauth"で簡単に実装できるので是非実装してみましょう。
今回はFacebookとGoogleだけ実装しましたが、GithubやTwiiterなどたくさんのSNSも同様のやり方で実装できます！！

#Gemのインストール
みなさんご存知のユーザー登録gem"devise"とOAuth関連のgemを入れていきます。'dotenv-rails'は環境変数を管理するgemなのでついでに入れておきましょう。

```ruby:Gemfile
gem 'devise'
gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'dotenv-rails'
```
```ruby:ターミナル
rails g devise:install
```
でデバイス関連のファイルを生成しておきましょう。

#OAuthの申請
FacebookとGoogleのKeyとSecretを取得します。

###Facebook
https://developers.facebook.com
①手順にしたがってアカウントとアプリを作成します。
②アプリを作成すればKeyとSecretは自動で生成されます。
<img width="1440" alt="スクリーンショット 2019-09-28 14.38.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/420475b0-cd48-8ed2-bd44-a8d6e242d3d6.png">
アプリ⇨設定⇨ベーシック


③リダイレクトURIの設定をします。
<img width="1440" alt="スクリーンショット 2019-09-28 14.45.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/c051c160-476e-c648-52df-29730c90ff9f.png">
Facebookログイン⇨設定⇨有効なOAuthリダイレクトURI

ここに

```
https://localhost:3000/users/auth/facebook/callback
```
と入力して、保存しておきましょう。

###Google
https://console.cloud.google.com
①GCPのプロジェクトを作成しAPI認証の設定からOauthクライアントIDを作成します。
<img width="528" alt="スクリーンショット 2019-10-18 0.11.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/deedc33c-47fb-6442-6366-ab449e0f0547.png">

②KeyとSecretは自動で生成されます。
![スクリーンショット 2019-10-18 0.13.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/51072b18-7044-ad9b-f8ba-c6cf906808f8.png)

③リダイレクトURIの設定をします。
<img width="722" alt="スクリーンショット 2019-09-28 15.00.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/4d63a693-2868-9082-8d4a-1c2dca9aa600.png">

ここに

```
http://localhost:3000/users/auth/google_oauth2/callback
```
と入力して保存しておきましょう。

ローカル環境で試すにはこの手順で大丈夫です。

#IDとSECRETの設定
一番上の階層（ルートディレクトリ）に.envというファイルを作成し、そこに先ほど手に入れたIDとSECRETを記載します。GoogleのIDは"~.com"まで入れてください。

```ruby:.env
FACEBOOK_CLIENT_ID='************'
FACEBOOK_CLIENT_SECRET='******************'
GOOGLE_CLIENT_ID='*************************************************'
GOOGLE_CLIENT_SECRET='*****************'
```
ここで初歩的な疑問。なぜ直接devise.rbに書き込まないのか。
理由は簡単で、GithubをPublicにしているからです。
APIのIDとSECRETはとてもセンシティブな情報で悪い人に知られると悪用され、多額の請求をされる可能性があります。
（ちなみに私は一度、IDとSECRETをそのままあげてしまい、Githubパトロールに叱られました。Githubありがとう:raised_hand::raised_hand:）
アプリケーションをGithubと連携させている場合はこの方法をお勧めします。
しかしこのままではGithubに上がってしまうので上げないようにしましょう。

```ruby:.gitignore
.env
```
これで大丈夫です。

#IDとSECRETの読み込み
先ほど設定したIDとシークレットを読み込みます。
ここで気をつけるのはgoogleの末尾に"skip_jwt: true"をつけること。
理由はOSのクロックのズレを修正する役割を持っています。この記述をしないと後にJWT::InvalidIatErrorがででしまいます。（私はここで１時間あたふたしました:sweat::sweat:）

```devise.rb
Devise.setup do |config|

〜省略〜

config.omniauth :facebook, ENV['FACEBOOK_CLIENT_ID'], ENV['FACEBOOK_CLIENT_SECRET']
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], skip_jwt: true

〜省略〜

end
```
#モデルの作成
モデルを作成します。
###①Userモデル

```ruby:ターミナル
rails g devise User
```
app/models/user.rb
config/routes.rb
db/migrate/20170211042541_devise_create_users.rb
test/fixtures/users.yml
test/models/user_test.rb

ファイルが生成されます。
**カラムは各自必要なものを入れてください！！**
ここでuser.rbの記述を変更しましょう。

```user.rb
class User < ApplicationRecord

has_many :sns_credentials, dependent: :destroy

devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: %i[facebook google_oauth2]

end
```
###②SNS_Credentialモデル
oauthの結果には、providerと各ユーザーを識別するuidが返ってきます。
次に、データにアクセスするためのaccess_tokenも返ってきます。
それを保存するテーブルを作成する必要があります。

```ruby:ターミナル
rails g model sns_credential
```
次にマイグレーションファイルの編集をします。

```********_create_sns_credentials.rb
class CreateSnsCredentials < ActiveRecord::Migration[5.2]
  def change
    create_table :sns_credentials do |t|
      t.string :provider
      t.string :uid
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end
```
```ruby:ターミナル
bundle install
```
アソシエーションも設定しておきましょう。

```sns_credential.rb
class SnsCredential < ApplicationRecord
  belongs_to :user
end
```

#コールバック関数
まずはdeviseのルーティングを変更します。

```routes.rb
Rails.application.routes.draw do
  devise_for :users,
  controllers: {
    sessions: 'users/sessions',
    registrations: "users/registrations",
    omniauth_callbacks: 'users/omniauth_callbacks'
  }
end
```
これでコールバック関数が呼び出せるようになりました。

```omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def facebook
    callback_for(:facebook)
  end

  def google_oauth2
    callback_for(:google)
  end
  

  def callback_for(provider)
    @omniauth = request.env['omniauth.auth']
    info = User.find_oauth(@omniauth)
    @user = info[:user]
    if @user.persisted? 
      sign_in_and_redirect @user, event: :authentication
      set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
    else 
      @sns = info[:sns]
      render template: "devise/registrations/new" 
    end
  end

  def failure
    redirect_to root_path and return
  end
end
```
ここではSNS認証を利用してユーザー登録していればサインインさせる。
していなければ、サインアップさせる処理をおこなっています。
find_oauthメソッドはモデルで定義していきましょう。

```user.rb
 def self.without_sns_data(auth)
    user = User.where(email: auth.info.email).first

      if user.present?
        sns = SnsCredential.create(
          uid: auth.uid,
          provider: auth.provider,
          user_id: user.id
        )
      else
        user = User.new(
          nickname: auth.info.name,
          email: auth.info.email,
        )
        sns = SnsCredential.new(
          uid: auth.uid,
          provider: auth.provider
        )
      end
      return { user: user ,sns: sns}
    end

   def self.with_sns_data(auth, snscredential)
    user = User.where(id: snscredential.user_id).first
    unless user.present?
      user = User.new(
        nickname: auth.info.name,
        email: auth.info.email,
      )
    end
    return {user: user}
   end

   def self.find_oauth(auth)
    uid = auth.uid
    provider = auth.provider
    snscredential = SnsCredential.where(uid: uid, provider: provider).first
    if snscredential.present?
      user = with_sns_data(auth, snscredential)[:user]
      sns = snscredential
    else
      user = without_sns_data(auth)[:user]
      sns = without_sns_data(auth)[:sns]
    end
    return { user: user ,sns: sns}
  end
```
これでサーバーサイド側の処理は完了です。

#view
最後にviewページを作成すれば完了です。
サインアップページ/サインインページに以下の記述を加えましょう。

```サインアップページ/サインインページ
= link_to user_facebook_omniauth_authorize_path do
= link_to user_google_oauth2_omniauth_authorize_path do
```
また今回のアプリケーションではSNS認証をした場合はpassword不要にしました。
しかし、deviseのデフォルト機能ではpasswordは必須です。
そこでヘルパーメソッドを使い、パスワードを自動生成することにしました。

```registrations_controller.rb
 password = Devise.friendly_token.first(7)
    if session[:provider].present? && session[:uid].present?
      @user = User.create(nickname:session[:nickname], email: session[:email], password: "password", password_confirmation: "password", f_name_kana: session[:f_name_kana],l_name_kana: session[:l_name_kana], f_name_kanji: session[:f_name_kanji], l_name_kanji: session[:l_name_kanji], birthday: session[:birthday], tel: params[:user][:tel])
      sns = SnsCredential.create(user_id: @user.id,uid: session[:uid], provider: session[:provider])
    else
      @user = User.create(nickname:session[:nickname], email: session[:email], password: session[:password], password_confirmation: session[:password_confirmation], f_name_kana: session[:f_name_kana],l_name_kana: session[:l_name_kana], f_name_kanji: session[:f_name_kanji], l_name_kanji: session[:l_name_kanji], birthday: session[:birthday], tel: params[:user][:tel])
    end
```
こんな感じですね。
SNSでサインアップした場合はpassword不要（passwordがなくても入れるため）
emailでサインアップした場合はpasswordが必要です。

#本番環境では？
ちなみに本番環境ではこのままだとSNS連携できません。
理由は2つあります。
①gitignoreによって本番環境でenvファイルが存在しないためIDとSECRETを読み込めない。
②リダイレクトURIをローカルでしか設定していない。

###①gitignoreによって本番環境でenvファイルが存在しないためIDとSECRETを読み込めない。
これの解決は簡単です。
本番環境の環境変数に入力しましょう。

```
FACEBOOK_CLIENT_ID='************'
FACEBOOK_CLIENT_SECRET='******************'
GOOGLE_CLIENT_ID='*************************************************'
GOOGLE_CLIENT_SECRET='*****************'
```

これで大丈夫です。

###②リダイレクトURIをローカルでしか設定していない。
これはリダイレクトURIをlocalhost:3000からドメインに変更。
その後、プライバシーポリシーページを追加して & StatusをONにすれば大丈夫です。

ただ、ここで注意しないといけないのはIPアドレスのままでは設定できないということです。
ドメインの取得とSSL化が必要です。
以下の記事を参考にしてください。
参考記事（https://qiita.com/nakanishi03/items/25278fb4dfad60ebfac4）


#終わりに
SNS認証はユーザビリティーの向上には大変役立ちます。
ボタン一つで登録できてしまうのは控えめに言って神です:relaxed::relaxed:
どんどん実装していきましょう。
記事を書くモチベーションになるので、もし参考になったらいいねお願いします:thumbsup_tone1::thumbsup_tone1: