【Raila】SorceryのExternalを使用してSNSログインを実装(Facebook認証)

# 環境

**注))** ローカル環境で動作確認するための実装です。

`Ruby 2.6.5`
`Rails 5.2.3`
`mysql 5.7.28`
`gem sorcery`
`gem font-awesome-sass`
`gem config`
`mkcert`
`credentials`

# 実装

Sorceryの導入、email・パスワードログイン機能は実装済みであることが前提です。
gemのbundleを済ませておいて下さい。

1. Facebook For Developersの設定
2. ローカル環境の設定

2つに分けて実装していきます。

## Facebook for developersの設定

### 1.Facebookアカウント作成

### 2.Facebook For Developersに登録・アプリ作成

下記資料を参考にしました。
参考資料:[Fantastech!!](https://fantastech.net/facebook-app-id-app-secret)

`アプリID、app secret`は後ほど使用しますので記載場所を確認しておいて下さい。
マイアプリ→設定→ベーシックに記載されています。

### 3.Facebookログインの設定

ダッシュボード上にある`Facebookログイン`の設定をクリック。
設定の`クライアントOAuth設定`の下記部分を確認。
・クライアントOAuthログイン→`はい`
・ウェブOAuthログイン→`はい`
・リダイレクトURIに制限モードを使用→`はい`
・有効なOAuthリダイレクトURI→`https://localhost:3000/oauth/callback?provider=facebook`
リダイレクトURIにはHTTPSが強制になるので後ほどローカル環境をSSL化します。

設定 → ベーシック → プライバシーポリシーのURLの指定。

>プライバシーポリシーとは、収集した情報をこれこれこういう目的で使いますよという旨が書かれた文書のことです。とりあえず開発時にはアクセスできるサイトであればなんでもよいです。アプリケーションを公開する段階になったら、忘れずに自分のアプリケーション内にプライバシーポリシーを載せたページを作り、それを指定しましょう。

#### OAuthとは

[![Image from Gyazo](https://i.gyazo.com/5c5ab227d66457fbef7aed27d5371f53.png)](https://gyazo.com/5c5ab227d66457fbef7aed27d5371f53)

この図は自分用にまとめたものです。
OAuthに関しては[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB)が最強です。

## ローカル環境の設定

基本は[wiki](https://github.com/Sorcery/sorcery/wiki/External)に沿って進めていきますが、流れを意識して記述するので順番が前後します。

### 1.externalインストール、DB反映

`external`をインストール

```console:ターミナル
$ rails g sorcery:install external --only-submodules
```

`migrationファイル`が生成されるので

```ruby:db/migrate/xxxxxxxx_sorcery_external.rb
class SorceryExternal < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, :uid, null: false
    
      t.timestamps
    end

    add_index :authentications, [:provider, :uid, :user_id]
  end
end
```

外部キーの`user_id`にindex張るのを忘れないように追加しておきましょう。

```console:ターミナル
$ rails db:migrate
```

### 2.Authenticationモデルの生成

`authentications`テーブルにはFacebook認証ログインしたユーザーデータが入ります。

```console:ターミナル
$ rails g model Authentication --migration=false
```

#### アソシエーション

`User`モデルと`Authentication`モデルの関連付けを行います。

```ruby:user.rb
class User < ActiveRecord::Base
  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications
end
```

```ruby:authentication.rb
class Authentication < ActiveRecord::Base
  belongs_to :user
end
```

### 3.oauthsコントローラー作成

```console:ターミナル
$ rails g controller Oauths oauth callback --skip-template-engine
```

`--skip-template-engine` → viewファイルスキップ

```ruby:oauths_controller.rb
class OauthsController < ApplicationController
  skip_before_action :require_login

  def oauth
    login_at(params[:provider])
  end

  def callback
    provider = params[:provider]
    if (@user = login_from(provider))
      redirect_to root_path, success: 'フェイスブックでログインしました'
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)
        redirect_to root_path, success: 'フェイスブックでログインしました'
      rescue StandardError
        redirect_to root_path, danger: 'ログインに失敗しました'
      end
    end
  end
end
```

ほぼwiki通りですがフラッシュメッセージにBootstrapを使用していますのでgemをいれてない方はwiki通りに進めて下さい。

### 4.ルーティング設定

```ruby:routes.rb
post 'oauth/callback', to: 'oauths#callback'
get 'oauth/callback', to: 'oauths#callback'
get 'oauth/:provider', to: 'oauths#oauth', as: :auth_at_provider
```

### 5.viewにFacebook認証ボタン配置

ボタンを表示させたい場所へ記述して下さい。

```ruby:xxxx.html.erb
<%= link_to auth_at_provider_path(provider: :facebook), class: 'facebook-btn' do %>
  <i class="fab fa-facebook-f"></i> Facebookログイン
<% end %>
```
### 6.サブモジュール(external)と設定の追加

その前にやるべきことがあります。
・ローカル環境でSSL暗号化通信を可能にするため`mkcert`を使用する
・keyとsecretの暗号化のために`credentials`に記述
・keyとsecretを定数管理するため`config`を使用する

SSL暗号化通信以外は設定しなくても動作しますが、設定することをおすすめします。

#### mkcertでSSL暗号化通信を可能にする

SSL暗号化通信については[こちら](https://qiita.com/hiro266/items/b9f7e6ee745225745b2c)にまとめましたのであやふやな人はのぞいてみてください。

mkcertの使い方は下記資料の中にある手順で進めます。
やることはmkcertを使用してSSL証明書を発行して、httpsでアクセスできるように設定します。
[【Rails】Facebookでユーザー認証する](https://qiita.com/t12u/items/52721d3ea2ce1e605ae9#mkcert%E3%81%A7ssl%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E4%BD%9C%E6%88%90)

一部修正。
開発環境下でのみhttpsアクセスできるよう制限をかけます。

```ruby:config/puma.rb
if Rails.env.development?
  ssl_bind "0.0.0.0", "3000", {
    cert: "config/certs/localhost.pem",
    key:  "config/certs/localhost-key.pem"
  }
end
```

#### credentialsを使用してkeyとsecretを暗号化

```console:ターミナル
$ EDITOR=vim bin/rails credentials:edit
```

credentials.yml.encがvimで開くので

```:credentials.yml.enc
# 追記
facebook_key: facebook for developersから 'アプリID' を参照して記述
facebook_secret: facebook for developersから 'app secret' を参照して記述
```

保存して再起動。

#### configを使用してkeyとsecretを定数管理

```ruby:config/settings/development.yml
facebook:
  key: <%= Rails.application.credentials.facebook_key %>
  secret: <%= Rails.application.credentials.facebook_secret %>
  callback_url: "https://localhost:3000/oauth/callback?provider=facebook"
```

ポイントはcredentialsを呼び出す際にerb記法で記述すること。`<%%>`で囲まないと動作しません。

これで3つの準備が終わったのでサブモジュールと設定の追加をしていきます。

#### サブモジュール(external)の追加

```ruby:config/initialzers/sorcery.rb
Rails.application.config.sorcery.submodules = %i[external]
```

#### 設定の追加

```ruby:config/initialzers/sorcery.rb
Rails.application.config.sorcery.configure do |config|
...
 config.external_providers = [:facebook]
...
  config.facebook.key = Settings.facebook.key
  config.facebook.secret = Settings.facebook.secret
  config.facebook.callback_url = Settings.facebook.callback_url
  config.facebook.user_info_mapping = {
    email: 'email', first_name: 'first_name', last_name: 'last_name'
  }
  config.facebook.user_info_path = 'me?fields=email,first_name,last_name'
  config.facebook.display = 'page'
  config.facebook.api_version = 'v2.3'
...
 config.user_config do |user|
...
  user.authentications_class = Authentication
```

ここの設定でFacebookからどんなユーザー情報が欲しいのかなど設定します。
実装終了。

# まとめ

wiki通り動かないとしんどい 笑

Deviseとの比較：[Rails でアカウントロジックを扱うなら sorcery が良いかも](https://qiita.com/ltcmdr927/items/90ba8b924ad65d773625)