Devise gemを使ってfacebook認証を実装する


##はじめに
会員登録画面を簡単に作ることの出来るRailsのdevise gemというものがあります。
このdevise gemを使ってfacebook認証を可能にするomniauthという機能があるらしいのでそれについて調べてみました。
以下は[deviseのwiki](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview), facebook exampleの和訳です

devise gem については[こちら](http://qiita.com/sasa0721/items/a0500f2f21b33b29beb6)

##使い方

まずはomniauth gem を追加してください。Gemfileに以下を記述

```
gem 'omniauth-facebook'
```
ここではfacebookの例を示しますが、他のomniauth gemでも同じように使うことが出来ます。一般的にgemの名前は "omniauth-provider"で、porviderのところにfacebook とか twitterとかに置き換えます。
[omniauth List](https://github.com/intridea/omniauth/wiki/List-of-Strategies)


Userモデルにproviderとuidというカラムを追加します

```
rails g migration AddColumnsToUsers provider uid
rake db:migrate
```
次にconfig/initializers/devise.rbにproviderの宣言とrequireを書く必要があります

```
require "omniauth-facebook"
config.omniauth :facebook, "APP_ID", "APP_SECRET"
```
requestのpermissionを変えるのなら"omniauth-* "のgemのread meを読んでください。

何らかの理由によりDeviseが　starategy classを読み込まない場合ははっきりと:strategy_classを明記することが出来ます。

```
config.omniauth :facebook, "APP_ID", "APP_SECRET", :strategy_class => OmniAuth::Strategies::Facebook
```
　
　
もしこのようなエラーが出たら

```
OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed):
```
必要なomniauthの証明キーがどこにあるのか明記する必要があります。[certified gem](https://github.com/stevegraham/certified)かメソッドを使ってください（これはあなたの使っているOSに依存します）

```
config.omniauth :facebook, "APP_ID", "APP_SECRET",
      :client_options => {:ssl => {:ca_path => '/etc/ssl/certs'}}
```

もしこのappをherokuでデプロイしたいのならこのような設定sectionを各必要があります

```
config.omniauth :facebook, "APP_ID", "APP_SECRET",
      {:scope => 'email, offline_access', :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}} 
```

Engin Yard Cloud serverは/etc/ssl/certs/ca-certificates.crtににあります.
OSXの開発においては証明書がfileではなくkeychainに置いてあるので簡単に証明書を無効にすることが出来ます。

```
require "omniauth-facebook"
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if Rails.env.development? 
config.omniauth :facebook, "APP_ID", "APP_SECRET"
```
errorについての深い議論は: https://github.com/intridea/omniauth/issues/260 でされています

strategyの設定をした後、model (e.g. app/models/user.r)をomniauthをつけることが必要です。

```
devise :omniauthable, :omniauth_providers => [:facebook]
```
(注意)既にrailas serverを起動していたのなら再起動してomniauthの設定を反映させることが必要です。

現在はDeviseでomniauth model を作ることを許可しただけです。Usertという名前のomniauthのでいるmodelが作られて、config/routes.rb,にdevise_for :usersが既に追加されているならDeviseは以下の様なurlメソッドを作ります。

- user_omniauth_authorize_path(provider)
- user_omniauth_callback_path(provider)

(注意)Deviseは *_url methodsを作る訳ではありません。決してディレクトリの上でcallback helperを使わないでください。facebook認証のためのlayoutの最初の一行に以下を記述するだけで大丈夫です

```
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
```
useromniauth_authorize_pathメソッドに変化する記号はDeviseの構成ブロックにmatchします。passをどうするか確認するためにも"omniauth-* " gem のREADMEをしっかり読んでください。

上のリンクをクリックするとuserはfacebookにredirecteされます。（もしこのリンクが存在しなければサーバーを再起動させようとします）証明書を得た後、あなたのapplicationのcallbackメソッドにridirectされます。callbackを実行するために一番最初にすることはconfig/routes.rb file に戻ってどのcontrollerでcallbackをじっこうするか決まることです。

```
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
```
私たちは "app/controllers/users/omniauth_callbacks_controller.rb":を新しく作ったとします。

```
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
```
このcallbackはproviderの名前と同じactionとして実行されます。ここではfacebook providerのためにcontrollerに追記します

```ruby:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_facebook_oauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      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
end
```

このactionはいくつかの特徴的な側面があります

1,omniauthによってfacebookから取得する情報すべてがrequest.env["omniauth.auth"]のようなhashで手に入る。どの情報が帰ってくるのか確認のために[Omniauth](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema)とomniauth-*  gemを確認してください。

2.有効なuserがmodelから送られた場合sign_inするべきです。flash messageにはdefaultのメッセージがsetされていることを覚えておいてください。次に、userがsign inしてredirectします。 :event => :authentication to the sign_in_and_redirect methodを使ったら強制的にすべてのauthenticationのcallbackメソッドがよばれます。

3.userが存在しない場合、Omniauth dataをsessionに保存します。私たちは"deviseというキー名でそれを使うことが出来ます。userがsign inしたときいつでも"devise"から始まるデータをすべて削除することが効果的です。ですから私たちは自動的にsessionをclean upします。最終的に登録フォームにredirectされます。

　
　
controllerが定義された後、find_for_facebook_oauth メソッドをmodel(app/models/user.rb)のなかで実行する必要があります

```
def self.find_for_facebook_oauth(auth)
  where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
    user.provider = auth.provider
    user.uid = auth.uid
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name   # assuming the user model has a name
    user.image = auth.info.image # assuming the user model has an image
    user.save!
  end
end
```

(注意) "User.new_with_session" によってDeviseの登録controllerが呼ばれる前にresourceが作られています。セッションからcopyする必要があるならsign_upの前にmodelのなかでnew_with_session を実行してuserがいつでも初期化されているということを意味します。これはfacebook emailをcopyする例です。

```
class User < ActiveRecord::Base
  def self.new_with_session(params, session)
    super.tap do |user|
      if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
        user.email = data["email"] if user.email.blank?
      end
    end
  end
end
```
最終的にFacebookでsign upすることをcancelすることを許可したいのなら "cancel_user_registration_path"にredirectさせることも出来ます。これはdeviseで始まるすべてのsessionデータを削除し、new_with_sessionは呼ばれない。

###logout link

```ruby:config/routes.rb
devise_scope :user do
   get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end
```

これで必要なことはすべて終わりました。時間があればtestなんかも書いてみてください。

https://github.com/intridea/omniauth/wiki/Integration-Testing

###omniauthを他のauthoticationなしで使う
もしあなたがomniauthだけをやりたいのならrouteに new_user_session (if not defined, root will be used)を定義する必要があります。下のものはその例です。（omniauth で他のauthoticationやdbを使うならこれは必要ありません）
```
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

devise_scope :user do
  get 'sign_in', :to => 'devise/sessions#new', :as => :new_user_session
  get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end
```
上の例ではsession controllerでは特別に何かをする必要はありません。