SNSのAPIを利用したユーザー新規登録・ログイン機能

##はじめに

前提として、アプリが既にdeviseを用いてメールアドレスとパスワードでログインできるようになっていること。

##API側の設定(Facebook)
https://developers.facebook.com/
にアクセスします。
[![Image from Gyazo](https://i.gyazo.com/35b4882e878be51d670b8a8f200ad484.png)](https://gyazo.com/35b4882e878be51d670b8a8f200ad484)
[マイアプリ]→[新しいアプリを追加する]で、上記のフォームが表示されます。
表示名とメールアドレスを入力して「アプリIDを作成してください」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/6780a7aaae410e0fd97fb29af8c41e4f.png)](https://gyazo.com/6780a7aaae410e0fd97fb29af8c41e4f)
上記のような画像が出ますから「設定」をクリック。

***
[![Image from Gyazo](https://i.gyazo.com/86ffc39f5e7a8f4f8f85f3a39f7e6594.png)](https://gyazo.com/86ffc39f5e7a8f4f8f85f3a39f7e6594)

この中から「ウェブ」をクリックします。
***
***
[![Image from Gyazo](https://i.gyazo.com/8dbcab57125a9896add43f6f871790b8.png)](https://gyazo.com/8dbcab57125a9896add43f6f871790b8)

今回はローカルでの設定なので、「http://localhost:3000/」と入力して「SAVE」します。

以上でFaceBookの設定は完了です。なお、画面左端の「設定」から「ベーシック」で「アプリID」と「app secret」を確認することができます。
***

##API側の設定（Google）
https://console.cloud.google.com/apis/credentials
をクリック。
ヘッダーの[プロジェクトの選択]→[新しいプロジェクト]をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/0e1de54a3c2908e3c50d47dd2959dd4e.png)](https://gyazo.com/0e1de54a3c2908e3c50d47dd2959dd4e)
任意のプロジェクト名を記入後、「作成」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/eee64d1d19ad12ffb5eb01e483952b84.png)](https://gyazo.com/eee64d1d19ad12ffb5eb01e483952b84)
画面左上のナビゲーションメニューから「APIとサービス」→「OAuth同意画面」をクリック。
遷移先で「外部」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/e1a7f349fc267dfbf596cac1aa898be7.png)](https://gyazo.com/e1a7f349fc267dfbf596cac1aa898be7)
任意のアプリケーション名を記入して最下部にある「保存」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/235223c228e890a3ef8f8bd085ef1b10.png)](https://gyazo.com/235223c228e890a3ef8f8bd085ef1b10)]
画面左上のナビゲーションメニューから「APIとサービス」→「認証情報」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/7c6616b5d8d3658cf6cc2e02228d2d9f.png)](https://gyazo.com/7c6616b5d8d3658cf6cc2e02228d2d9f)
「+認証情報を作成」→「OAuthクライアントID」をクリック。
***
[![Image from Gyazo](https://i.gyazo.com/fe03040b10f7cb5e1240faafd6ab3675.png)](https://gyazo.com/fe03040b10f7cb5e1240faafd6ab3675)

[ウェブアプリケーション]を選択し、任意の「名前」を記入し、「承認済みのリダイレクトURI」は、[http://localhost:3000/users/auth/google_oauth2/callback]
を記入して、「作成」をクリック。
***
Google+ APIを有効にする必要があるので、[APIとサービス]→[ライブラリ]を選択し、遷移先のページで[google+]を検索しましょう。
[![Image from Gyazo](https://i.gyazo.com/d2617dceec2cb3fd7938973fc838dc3a.png)](https://gyazo.com/d2617dceec2cb3fd7938973fc838dc3a)
検索結果として表示される[Google+ API]をクリックしてください。遷移先のページで[有効にする]を選択して完了です。
***
なお、googleの「クライアント ID」と「クライアント シークレット」は「認証情報」から確認できます。
***
##Gemの導入
gemファイルの導入

```Gemfile
$ gem 'omniauth-facebook'
$ gem 'omniauth-google-oauth2'
$ gem "omniauth-rails_csrf_protection"
```
```ターミナル
$ bundle install
```
##環境変数の設定

```$ vim ~/.bash_profile```を実行し環境変数を設定する

OSがCatalina以降なら

```$ vim ~/.zshrc```を実行。

```vim
export FACEBOOK_CLIENT_ID='メモしたアプリID'
export FACEBOOK_CLIENT_SECRET='メモしたapp secret'
export GOOGLE_CLIENT_ID='メモしたクライアントID'
export GOOGLE_CLIENT_SECRET='メモしたクライアントシークレット'
```
変更後は```$ source ~/.bash_profile```を実行。

環境変数が設定できたら、アプリ側にそれを読み込む記述を加えます。

```config/initializers/devise.rb
config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']
```

##SNS認証に必要なモデル、コントローラを用意

「Facebookで登録」「Googleで登録」をクリックでWeb API側にリクエストを送ります。
次にAPI側からSNS上に登録されている情報が返ってきます。
レスポンスの中の、uidとprovider情報をSnsCredentialという名前のデータベースに、ユーザー情報（誕生日やフルネームなど）とともに保存します。

なので、まずSnsCredentialモデルを作成します。

```ruby:ターミナル
$ rails g model sns_credential provider:string uid:string user:references
```

Userモデルとのアソシエーションのために、外部キーとしてuser_idを持たせておきます。
***
アソシエーションとオプションを記述します。

```models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

  has_many :sns_credentials
```
```models/sns_credential.rb
belongs_to :user, optional: true
```
***
続いて、deviseのコントローラをカスタマイズするためのコントローラを作成します。

```ruby:ターミナル
$ rails g devise:controllers users
```

controllers/users以下にdeviseのクラスを継承したコントローラが生成されます。
このコントローラ内で各アクションをオーバーライドすることによって、deviseが提供するアクションの内容をカスタマイズすることができます。

このコントローラを使用させるためにdeviseのルーティングを変更します。

```config/routes.rb
devise_for :users, controllers: {
  omniauth_callbacks: 'users/omniauth_callbacks',
  registrations: 'users/registrations'
}
```
***

環境変数の読み込みができているかをrails cで確認。以下のようになればOKです。

```ruby:ターミナル
$ rails c
irb(main):001:0> ENV['FACEBOOK_CLIENT_ID']
=> "1111111111111111"  # 数字だけ
irb(main):002:0> ENV['FACEBOOK_CLIENT_SECRET']
=> "1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"  # 数字と文字列
irb(main):003:0> ENV['GOOGLE_CLIENT_ID']
=> "~~~~~~~googleusercontent.com"  # 末尾がこの形
irb(main):004:0> ENV['GOOGLE_CLIENT_SECRET']
=> "1a1a1a1a1aa1_1a1a1a1_1a"       #  英数字と_(アンダーバー)が含まれる
```

##ビューを実装する

任意の場所に以下を貼り付けてください。

```
= link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post

= link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post
```
user_facebook_omniauth_authorize_pathやuser_google_oauth2_omniauth_authorize_pathは、API側にリクエストを送るためのパスです。これによって、omniauth_callbacks_controller.rbのfacebookアクションとgoogle_oauth2アクションを動かすことができます。

##メソッドを実装する
omniauth_callbacksコントローラにSNSに登録されている情報が返されます。
SNSの情報から、ユーザー情報のみを取得して、既存のユーザー情報と照合を行う

```controllers/users/omniauth_callbacks_controller.rb
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  def failure
    redirect_to root_path
  end

  private

  def authorization
    sns_info = User.from_omniauth(request.env["omniauth.auth"])
    @user = sns_info[:user]

    if @user.persisted? #ユーザー情報が登録済みなので、新規登録ではなくログイン処理を行う
      sign_in_and_redirect @user, event: :authentication 
    else #ユーザー情報が未登録なので、新規登録画面へ遷移する
      @sns_id = sns_info[:sns].id
      render template: 'devise/registrations/new'
    end

```
facebook、google_oauth2というアクションから目的の処理authorizationを呼び出します。
APIから受け取ったレスポンスがrequest.env["omniauth.auth"]という変数に入っています。
DB操作を行うメソッドUser.from_omniauthを仮で作り、変数request.env["omniauth.auth"]を渡します。
このメソッドにはUserモデルのインスタンスを返させて＠userへ代入することで、その後の処理でdeviseのヘルパーを利用することができる。

##User.from_omniauthメソッドをモデルで定義する
```user.rb
def self.from_omniauth(auth)
  sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
  # sns認証したことがあればアソシエーションで取得
  # 無ければemailでユーザー検索して取得orビルド(保存はしない)
  user = sns.user || User.where(email: auth.info.email).first_or_initialize(
    nickname: auth.info.name,
      email: auth.info.email
  )
  # userが登録済みの場合はそのままログインの処理へ行くので、ここでsnsのuser_idを更新しておく
  if user.persisted?
    sns.user = user
    sns.save
  end
  { user: user, sns: sns }
end
```
##新規登録画面でのビューを用意する
```views/devise/registrations/new.html.haml
  - if @sns_id.present?
    = hidden_field_tag :sns_auth, true
  - else
    .field
      = f.label :password
      - if @minimum_password_length
        %em
          (#{@minimum_password_length} characters minimum)
      %br/
      = f.password_field :password, autocomplete: "new-password"
    .field
      = f.label :password_confirmation
      %br/
      = f.password_field :password_confirmation, autocomplete: "new-password"
```
@sns_idが存在すればhidden_fieldでparams[:sns_auth]としてtrueという値を送信し、存在しなければパスワード入力欄を表示します。
このparamsは通常devise/registrations#createアクションへ送信されますが、今回はこのコントローラをオーバーライドするためにcontrollers/users/registrations_controller.rbを作成してあります。
このコントローラ内でcreateアクションの動作を変更しましょう。

```controllers/users/registrations_controller.rb
  def create
    if params[:sns_auth] == 'true'
      pass = Devise.friendly_token
      params[:user][:password] = pass
      params[:user][:password_confirmation] = pass
    end
    super
  end
```

params[:sns_auth]が送られて来た時だけDevise.friendly_tokenを使ってパスワードを自動生成し、あとの処理はsuperメソッドでdeviseに任せます。

***
以上です。このやり方でエラーが出た、詰まった方は、コメントいただけるとありがたいです！