Rails5でFacebookログイン実装する

# はじめに
今作っているアプリケーションでFacebookログインすることにしたので、調べながら実装した。
先人の記事とREADMEを読みながらすすめると割りとすぐにできたので人の優しさに感謝

# 実装

## Gemのインストール
まずは必要なGemのインストールを行う。
今回はdeviseとfacebook-oauthを使って実装するのでこの2つをGemfileに追加する。

```
gem 'devise'
gem 'omniauth-facebook'
```

追加したらbunldeを実行する

```
$ bundle
```


# deviseの設定ファイルを生成する
それでは、deviseの設定ファイルなどを生成する。
ターミナルで以下を実行する。

```
$ rails g devise:install
```

## deviseのモデルを生成する
今回はUserモデルを作成した。

```
$ rails g devise user
```

生成されたマイグレーションファイルで管理したいユーザーのカラムを追加する。
あと、このカラムは必須なので忘れずに追加する。

```
t.string :provider
t.string :uid
```

マイグレーションファイルの編集が済んだらmigrateを実行する。

```
$ bundle exec rake db:migrate
```

## Facebookにアプリを登録する
Facebookにアプリを登録してidとsecret_idをもらってくる。

ここにアクセスして自分のアプリを登録する。
https://developers.facebook.com/

## deviseに設定
devise.rbにFacebookに登録したアプリのapp idとapp secretを登録する。
べたでの登録は危ないので、secrets.ymlとか環境変数とかに書いたのを読み込むようにすると良さげ。

```ruby:config/initializers/devise.rb
Devise.setup do |config|
  config.omniauth :facebook, Rails.application.secrets.facebook[:app_id], Rails.application.secrets.facebook[:app_secret]
```

callback_urlが必要なこともある

```ruby:config/initializers/devise.rb
config.omniauth :facebook,
    Rails.application.secrets.facebook[:app_id], Rails.application.secrets.facebook[:app_secret],
    callback_url: Rails.application.secrets.facebook[:callback_url]
```
## モデルをomuniauthableにする
デフォルトではomuniauthを使えるようになっていないので、モデルに記述を追加して使えるようにする。

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

## ルーティングを追加する
Facebookの認証後に処理を行うためのコールバックに必要なUルーティングを追加する。

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

## リンクを表示する
Facebookの認証に飛ばすためのURLをもったリンクを任意のビューに追加する。
使っているテンプレートエンジンがSlimなもので、サンプルコードもSlimで失礼

```ruby:views/layouts/_header.html.slim
= link_to 'Facebookでログイン', user_facebook_omniauth_authorize_path
```

## コールバック時の処理を行う
コールバックが呼ばれたときに行う処理を記述する。
コントローラを用意する。
ルーティングでusers/omuniauth_coallbacksとしているので、`app/controllers/users/omniauth_callbacks_controller.rb`を追加する。

```ruby:app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
```

コールバックは、プロバイダと同じ名前のアクションを定義してその中で実装する。

```ruby:app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    @user = User.from_omniauth(request.env['omniauth.auth'])
    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication
      set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
    else
      session['devise.facebook_data'] = request.env['omniauth.auth']
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path, alert: 'Facebook認証に失敗しました。'
  end
end
```

### ソースコードについて
1. `request.env["omniauth.auth"]`にOmniAuthがFacebookから取得した情報がハッシュとして登録されている。
2. 有効なユーザーが見つかると、Deviseメソッド（sign_inまたはsign_in_and_redirect）でログインできる。
3. ユーザーが永続化されていない場合は、OmuniAuthのデータをセッションに格納する。データは、deviseをキー名前空間として使用して保存する。deviseがdeviseで始まるキーのデータ尾を削除してくれるので便利。毎回自動的にリフレッシュされたセッションを得ることができる。

## モデルにfrom_omniauthメソッドを定義する
app/controllers/users/omniauth_callbacks_controller.rbにfrom_omniauthメソッドがあるけど、まだ定義していないので、Userモデルにメソッドを定義する。
providerとuidで検索して、存在してればデータを取得し、なければ、データを保存するようにしている。
nameやavatarは該当するカラムが必要。

```ruby:models/user.rb
def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.name = auth.info.name
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
      user.avatar = auth.info.image
    end
  end
```

# 最後に
自分はこれで上手くいった、、、、と思っている
もし上手く行かなかった場合や、間違いや、ここはこうした方がよいなどあったら教えてください。

## 参考
[GitHub devise](https://github.com/plataformatec/devise)
[GitHubdevise（Wiki OmuniAuth）](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview)
[Devise+OmniAuthでユーザ認証を実装する手順](http://qiita.com/kami_zh/items/94aec2d94a2b4e9a1d0b)
[Fecebookログインが急にエラーになった時の対応](http://qiita.com/tosiooooooo/items/845621e033778ca2d6e9)

## 疑問
providerとuidで検索するならindex貼ったほうが良いのかな？？