RailsでFacebook認証する（Devise + OmniAuth）

#環境
Rails 4.2.6
Devise 4.1.1
omniauth-facebook 3.0.0

#やりたいこと
・Facebook認証をする
・Userモデルにnameカラムを追加する

#Facebook認証する
##1: 認証機能をインストール（Devise）
[前も書きました](http://qiita.com/seimiyajun/items/96441cca00d8bf9f97e6)がdeviseの導入方法については詳しい記事がたくさんあるのでそちらを参考にしてください。
[Qiita:Deviseの設定手順をまとめてみた。 その1　導入編](http://qiita.com/itboze/items/ea0d9236d89fa24a7a1c)
とか
[Qiita:Rails 4.2 で ユーザー管理・認証 (devise)](http://qiita.com/Salinger/items/873e3c667462746ae707)
とか。
まぁ[DeviseのGithub](https://github.com/plataformatec/devise)を参考にするのがベターだとは思います。

##2: Facebook認証をするためのapp登録をする
[Facebook Developers](https://developers.facebook.com/)より登録し、「新しいアプリを追加」します。

##3: ローカル環境からからアクセスするための設定。
設定＞ベーシック＞アプリドメインにローカル用の適当なドメインを追加します。→　`test.example.com`
Facebookログイン＞有効なOAuthリダイレクトURIに先ほどのドメインを追加します。→`test.example.com`

開発環境のhostsファイルにlocalhostをtext.example.comとしてアクセスできるよう追記します。

Macなら`/private/etc/hosts`に対して、
Windowsなら`c:¥windows¥system32¥drivers¥etc¥hosts`に対して以下を追記します

```hosts:hosts
127.0.0.1       test.example.com
```

##4: Facebook認証をインストール（omniauth-facebook）
omniauth-facebookでやります。
[omniauth-facebookのgithub](https://github.com/mkdynamic/omniauth-facebook)もあるのですが、Deviseと連携させる場合は、[Deviseのwiki](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview)を見た方が良いです。

やることはこんな感じ
1. gem追加＆bundle install
2. omniauth用のproviderカラムとUIDカラムをUserモデルに追加
3. Userモデル他を編集する

###4-1.gem追加＆bundle install

```ruby:Gemfile
gem 'omniauth-facebook'
```

```sh
$ bundle install
```

###4-2.omniauth用のproviderカラムとUIDカラムをUserモデルに追加
```sh
$ rails g migration AddOmniauthToUsers provider:string uid:string
$ rake db:migrate
```

###4-3.Userモデル他を編集する
deviseのomniauthableを使えるようにする

```ruby:app/models/user.rb
devise :omniauthable, :omniauth_providers => [:facebook]
```

ルーティングにcallbackとログアウトを追加する

```routes.rb
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  devise_scope :user do
    delete :sign_out, to: 'devise/sessions#destroy', as: :destroy_user_session
  end
```

callbackを作成する
Wikiに書いてある通りでも良いのですが、私はシンプルにこんな感じでつくっています。

```/app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  include Devise::Controllers::Rememberable
  
  def facebook
    auth = request.env['omniauth.auth']
    user = User.find_or_create_by(
      provider: auth.provider,
      uid: auth.uid
    )

    remember_me(user)

    sign_in_and_redirect user, event: :authentication
  end

  def failure
    redirect_to root_path
  end
end
```

ログインリンクを追加する

```ruby:/app/views/適当.html.erb
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
```

##4: Deviseから不要な機能を削除する
Userモデルよりrememberableとtrackableを除く機能を削除。
先ほどのomniauthableとまとめるとこんな感じ。

```ruby:app/models/user.rb
devise :rememberable, :trackable, :omniauthable, omniauth_providers: [:facebook]
```

これでtest.example.comへアクセスすれば使えます。

#ユーザーモデルにnameカラムを追加する
手順はこんな感じ
1. ユーザー名（name）カラムを追加する
2. callbackを修正する
3. ユーザー名編集画面を追加する

##1.ユーザー名（name）カラムを追加する
```sh
$ rails g migration AddNameToUsers name:string
$ rake db:migrate
```

##2.callbackを修正する
nameは編集される可能性があるので、find_by_or_createは使わずにfind_byとcreateを分ける。

```/app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  include Devise::Controllers::Rememberable
  
  def facebook
    auth = request.env['omniauth.auth']
    user = User.find_by(
      provider: auth.provider,
      uid: auth.uid
      ) || User.create(
      provider: auth.provider,
      uid: auth.uid,
      name: auth.info.name
    )
    
    remember_me(user)

    sign_in_and_redirect user, event: :authentication
  end

  def failure
    redirect_to root_path
  end
end
```

##3.ユーザー名編集画面を追加する
こんな感じで。

```app/views/users/edit.html.erb
<h2>プロフィールを編集する</h2>

<%= form_for(current_user, html: { method: :patch }) do |f| %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>

  <div class="actions">
    <%= f.submit '更新する' %>
  </div>
<% end %>

<h3>退会する</h3>

<%= button_to "退会する", user_path( current_user ), data: { confirm: "本当に退会してよろしいですか？?" }, method: :delete %>
```

※registerableを使っていないのでユーザーの編集/削除は自前で実装する。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [ :update, :destroy ]
  
  def edit
  end
  
  def update
    if @user.update( user_params )
      redirect_to root_path
    else
      render :edit
    end
  end
  
  def destroy
    if @user.destroy
      redirect_to root_path
      else
      render :edit
    end
  end
  
  private
  def set_user
    @user = User.find( params[:id] )
  end
  
  def user_params
    params.require(:user).permit( :name )
  end
end
```

ルーティングは`:edit`, `:update`, `:destroy`のみ。

```routes.rb
  resources :users, only: [ :edit, :update, :destroy ]
```

#Devise/registerableを使う場合
※Facebookによる登録とメールアドレスによる登録を併用する場合。

nameカラムを通すためにApplicationControllerにStrongParametersを追加しましょう。
（以下のコードはユーザー登録時とユーザー情報更新時に使う場合）

```app/controllers/application_controller.rb
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) << :name
    devise_parameter_sanitizer.for(:account_update) << :name
  end
```

ルーティングも追加

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

パスワードカラムを持たないとDevise経由でのユーザーは更新できないため、以下のメソッドを用意して（上書きして）おきましょう。

```app/controllers/regitrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  protected
  def update_resource(resource, params)
    resource.update_without_current_password(params)
  end
end
```

自前の`views/users/...html.erb`は使わないので削除しておきましょう。

#リンク
[EasyRamble: Railsの認証プラグインDeviseでのStrongParametersについて](http://easyramble.com/strong-parameters-on-rails-devise.html)
[iii ThreeTreesLight: [rails][devise] devise userの更新対象追加 + current_password無し更新](http://threetreeslight.com/post/81849015358/railsdevise-devise-user%E3%81%AE%E6%9B%B4%E6%96%B0%E5%AF%BE%E8%B1%A1%E8%BF%BD%E5%8A%A0)
[Web屋さんのアレ: 【Facebook】Facebookアプリでlocalhostを指定する為のアレ【アプリ】](http://odokuchang.blogspot.jp/2012/01/facebookfacebooklocalhostwo.html)
[Simplie Post: RailsでDevise+OmniAuthによりメールアドレスを使わない認証を実装する方法](http://post.simplie.jp/posts/43)