deviseでfacebook,twitter認証

**追記：[Qiita「TwitterAPI Devise連携/グラフ可視化/データの効率的格納/API高速化」](http://qiita.com/items/ea2c5f7faec66347a10f) にて続きを書きました。**


qiitaにもある、「twitterでログイン」を実装します。
devise(railsのユーザー認証用gem)を使ってtwitter,facebookなどのOAuth認証。

今回はOAuth認証のみでdevise本来の追加認証はしません。tokenも使いません。

deviseの導入から解説していきます。

#環境
- ruby(1.9.3)
- rails(3.2.3)
- devise(2.0.4)
- omniauth (1.1.0)
- omniauth-oauth (1.0.1)
- omniauth-oauth2 (1.0.2)
- omniauth-facebook (1.3.0)
- omniauth-twitter (0.0.9) 

#devise+omniauth認証
まずはdeviseの導入。https://github.com/plataformatec/devise を参考に。

##1. Gem
以下をgemに追加

```ruby:Gemfile
gem 'devise'
#omniauth
gem 'omniauth-facebook'
gem 'omniauth-twitter'
```


##2. devise導入

```console:
$ bundle install
$ rails g devise:install
　config/inistializer/devise.rb (設定ファイル)作成

$ rails g devise user
　認証用モデル作成。今回はUserモデルにした。

$ rails g migration AddOmniToUser uid:integer
　migrationファイルにて、omniauth認証用カラム追加
```


##3. deviseのmigration設定
migrationファイルを編集します。Omniauthableのためのカラムを追加します。
今回はtrackable以外の機能は使用しないので、その他はコメントアウトしてください。使う場合はお好みで。
trackableはログイン回数などを記録する機能です。

**facebookはuidが非常に長いので、bigintにすることに注意してください！(3日はまりました)**

```ruby:db/migrate/*_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|

      ## Trackable
      t.integer  :sign_in_count, :default => 0
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ##Omniauthable
      t.integer :uid, :limit => 8 #bigintにする
      t.string :name
      t.string :provider
      t.string :password


      t.timestamps
    end
    add_index :users, :uid,  :unique => true
```

```console:
$ rake db:migrate
```


##4. deviseのモデル編集
Userモデルを編集します。omniauthableにし、使わない機能をコメントアウトしましょう。

```ruby:app/models/user.rb
class User < ActiveRecord::Base
   devise :trackable, :omniauthable
   attr_accessible :name, :password, :uid, :provider
end
```

##5.developerサイトに登録

OAuth認証したいサイトにてアプリを作成し、IDとsecretを取得します。
twitter,facebookのdeveloperサイトでの作成法を解説。
サイトのURLを設定するので、development,production用に2つ作ると良いです。
(**ここで指定するURLが異なると動かない場合があります！**)

###facebook

http://developers.facebook.com/
一番上のバーにある「アプリ」→右上「新しいアプリケーションを作成」
name,descriptionを記述。
herokuで作るかどうか選べますが、今回関係ないのでチェックしません。

アプリの個別ページに飛ぶので、
「アプリをFacebookに結合する方法を選択してください」でサイトのURLを記述します。
http://localhost:3000/
とでもしておきましょう。

App IDとApp Secretをメモっておきます。

###twitter

https://dev.twitter.com/
ログイン後、右上のアイコンから「My Application」→「Create a New Application」で詳細を入力します。
name,descriptionを記述。websiteには実際に動かすURLのルートを。Callback URLにも同様にURLを記述。
Consumer key,Consumer secretをメモってください。

- 注意:http://localhost:3000/ だとエラー出ます。ドメインやIPを入れてください。(なお
twitterの場合、facebookと異なり、ここのURL指定が適当でも動いたりします)
- 注意:Callback URL(ログイン完了後のredirect URL)を記述しないと動きません。なぜかここで記述したURLにはredirectされないので、なんでもいいので記述を。

##6. deviseのconfig編集

OAuth認証のために、deviseを設定します。
config/initializer/devise.rb
に記述してもいいのですが、開発環境と本番環境を分けたいため、development.rbに記述します。

```ruby:config/environments/development.rb
.
.
.
  Devise.setup do |config|devise@example.com"
    config.omniauth :facebook, '{ID}', '{SECRET}', :scope => 'email,user_birthday', :display => 'popup'
    config.omniauth :twitter, '{ID}', '{SECRET}', :display => 'popup'
  end
```

facebookではメールと誕生日も取得できるようにしてありますが、不要な場合はscopeを消してください。

##7. Userモデル再編集
各種関数を追加します。

```ruby:app/models/user.rb
.
.
.
  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"]
      end
    end
  end
  
  
  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:auth.extra.raw_info.name,
                          provider:auth.provider,
                          uid:auth.uid,
 #                          email:auth.info.email, #emailを取得したい場合は、migrationにemailを追加してください。
                          password:Devise.friendly_token[0,20]
                          )
    end
    user
  end
  
  
  def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:auth.info.nickname,
                          provider:auth.provider,
                          uid:auth.uid,
#                          email:auth.extra.user_hash.email, #色々頑張りましたがtwitterではemail取得できません
                          password:Devise.friendly_token[0,20]
                          )
    end
    user
  end

```


##8. Controller設定

app/controllers/users/omniauth_callbacks_controller.rbを作成し、編集します。

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

    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
  
  def twitter
    # You need to implement the method below in your model
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user)

    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end
```


##9. route定義
config/routes.rbを設定します。
ログイン後にはrootに飛ばされるので、root定義もすると良いです。

```ruby:routes.rb
  root :to => 'yourapp#index'
  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

.
.
.

```

##10. 確認
viewを作り、サーバーをたちあげて確認。

```app/views/yourapp/index.html.erb
            <ul class="nav">
              <% if user_signed_in? %>
                <li><%= link_to 'ログアウト', destroy_user_session_path %></li>
              <% else %>
                <li><%= link_to "facebookでログイン", user_omniauth_authorize_path(:facebook) %></li>
                <li><%= link_to "twitterでログイン", user_omniauth_authorize_path(:twitter)  %></li>
              <% end %>
                
            </ul>
```

Invalid URI error が出る場合は、twitterやfacebookのURL指定が間違っています。
facebookの場合は、設定変更の反映に時間がかかる場合があります。
どうしてもダメだったらfacebook上のアプリを作りなおしてください。

##11. 最後に

長かったですがお疲れ様でした！
他のOAuth認証も、似たような形で追加してください。