2015年2月4日水曜日

RailsでFacebookログイン(OAuth)を実装する


概要

RailsでFacebookログイン(OAuth)を実装する。



動作環境

  • Ruby on Rails 4.2.0
  • Ruby 2.1.0p0 
  • Devise 3.4.1
  • OmniAuth 1.2.2
  • OmniAuth-facebook 2.0.0
  • OmniAuth-twitter 1.1.0

事前準備

FacebookのAPIキーを取得する。

https://developers.facebook.com でアプリをつくる。
作成が完了したら、設定より「Add Platform」→「Website」を選択する。
サイトURLにURLを入力する。

Gemfile
gem 'omniauth-facebook'
  
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
end
  
FACEBOOK_KEYとFACEBOOK_SECRETは環境変数なので、CentOSとかの場合は ~/.bash_profile なんかを編集する。

ルーティングの設定

failureは、なんらかの原因でログインが失敗した時に、表示させるページ。
ログインボタンのリンクは/auth/facebookです。
get '/auth/:provider/callback',    to: 'users#create',       as: :auth_callback
get '/auth/failure',               to: 'users#auth_failure', as: :auth_failure
  

コントローラー

Facebookから返ってくる情報を使って、ユーザーを作成したり、ログインさせたりします。
ログインの度に、iconや名前を更新したいので、@user.saveしています。
sign_inメソッドについては説明しませんが、current_userに該当のuserを代入しているだけです。
  
from_omniauthメソッドやcontext: :facebook_loginについてはモデルにて。
env['omniauth.auth']にいろいろ情報が入っているので、引数として渡しています。
controllers/users_controller.rb
    def create

        if env['omniauth.auth'].present?
            # Facebookログイン
            @user  = User.from_omniauth(env['omniauth.auth'])
            result = @user.save(context: :facebook_login)
            fb       = "Facebook"
        else
            # 通常サインアップ
            @user  = User.new(strong_params)
            result = @user.save
            fb       = ""
        end
        if result
            sign_in @user
            flash[:success] = "#{fb}ログインしました。" 
            redirect_to @user
        else
            if fb.present?
                redirect_to auth_failure_path
            else
                render 'new'
            end
        end
    end
  

モデル

同じメールアドレスが見つかれば、そのユーザーとしてログイン。
そうでなければ、新しくユーザーを作成します。
Facebookログインでは、パスワードが必要ありませんので、パスワードのバリデーションを無効にしています。
on: :facebook_loginを加えておいて、コントローラーでセーブする際に@user.save(context: :facebook_login)としています。
models/user.rb
    validates :password, presence: false, on: :facebook_login

    def self.from_omniauth(auth)
        # emailの提供は必須とする
        user = User.where('email = ?', auth.info.email).first
      if user.blank?
        user = User.new
      end
    user.uid   = auth.uid
    user.name  = auth.info.name
    user.email = auth.info.email
    user.icon  = auth.info.image
    user.oauth_token      = auth.credentials.token
    user.oauth_expires_at = Time.at(auth.credentials.expires_at)
    user
    end
  
/auth/facebook を叩けば、Facebookのダイアログが表示される。

1. メールアドレスによる認証の実装

Gemfileの編集

Gemfile
gem 'devise'

インストール

$ bundle install

Deviseの各ファイルを生成

$ rails g devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

===============================================================================


Some setup you must do manually if you haven't yet:


  1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb:

    config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.


  2. Ensure you have defined root_url to *something* in your config/routes.rb.

     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.

     For example:

      
<%= notice %>

      
<%= alert %>

  4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:


       config.assets.initialize_on_precompile = false


     On config/application.rb forcing your application to not access the DB

     or load models when precompiling your assets.

  5. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

Deviseを日本語化

config/locales/devise.en.ymldevise.ja.ymlにリネームする。
以下の内容をコピペする。
もちろんRailsアプリ自体も日本語化しておく必要がある。
  • devise-i18n/ja.yml at master · tigrish/devise-i18n
config/locales/devise.ja.yml
ja:
  devise:
    confirmations:
      confirmed: アカウントを登録しました。
      send_instructions: 登録方法を数分以内にメールでご連絡します。
      send_paranoid_instructions: メールアドレスが登録されていれば、数分以内にアカウントを確認する方法が記載されているメールが届きます。

          :

Userモデルを生成

Deviseのジェネレータを利用することで、あらかじめモジュールが定義されたモデルファイルを生成することができる。
$ rails g devise User

利用するモジュールを編集

Userモデルでは、deviseメソッドによりどのモジュールを利用するかを定義できる。
Fasebook認証を可能とするためdeviseメソッドに:omniauthableも追加しておく。

app/models/user.rb
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable,
       :omniauthable, omniauth_providers: [:facebook]


利用できるモジュールについてはREADMEを参照。

マイグレーションの実行

rails g devise Userしたときにdb/migrate/xxx_devise_create_users.rbという名前でマイグレーションファイルも生成されている。
これをDBに適用する。
$ rake db:migrate
== 20150207093749 DeviseCreateUsers: migrating ================================-- create_table(:users)
   -> 0.0800s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0659s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0528s
== 20150207093749 DeviseCreateUsers: migrated (0.1993s) =======================


サインアップフォームの表示

以上で、http://〜/users/sign_upにアクセスするとフォームが表示される。
この時点でユーザ登録ができる状態になっている。

2. OAuthでの認証

Gemfileの編集

Gemfile
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'

インストール

$ bundle install

8.Authenticationモデルの作成

元々あるUserモデルにカラムを追加しても良いが、複数のサービスのアカウントで同一のアカウントにログインできるようにしたいので、Userモデルが has_many する Authentication モデルを作成する。
$ rails g model authentication user_id:integer provider:string uid:string
      invoke  active_record

      create    db/migrate/20150208043543_create_authentications.rb

      create    app/models/authentication.rb

      invoke    test_unit

      create      test/models/authentication_test.rb

      create      test/fixtures/authentications.yml


Userモデルを”has_many”とし、 AuthenticationモデルをUserモデルに”belongs_to”とする。
app/models/user.rb

class User < ActiveRecord::Base
  has_many:authentications, dependent: :destroy
end

app/models/authentication.rb

class Authentication < ActiveRecord::Base
  belongs_to :user
end

マイグレーションの実行

$ rake db:migrate
== 20150207101521 AddColumnsToUsers: migrating ================================

-- add_column(:users, :uid, :string)

   -> 0.1305s

-- add_column(:users, :provider, :string)

   -> 0.1113s

== 20150207101521 AddColumnsToUsers: migrated (0.2424s) =======================


APIキーの取得

Facebook

以下よりアプリケーションを作成する。
作成が完了したら、設定より「Add Platform」→「Website」を選択する。
サイトURLにURLを入力する(例:http://192.168.33.10:3000/)。

Twitter

以下よりアプリケーションを作成する。
作成が完了したら、「Settings」より以下の設定を行なう。
  1. Callback URL
    • 例:http://〜/users/auth/twitter
  2. 以下にチェックを入れる:
    • Allow this application to be used to Sign in with Twitter
参考

Deviseの設定

上記で取得したプロバイダの各キーを設定する。
セキュリティのために各APIキーは環境変数に設定する。
開発/ステージング/本番環境と分ける場合はRails.env.production?などを用いて振り分ける。
config/initializers/devise.rb
Devise.setup do |config|
  # ...

  config.omniauth :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_SECRET']
  config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

Userモデルにfindメソッドを実装

uidproviderの組み合わせは一意であり、これによりユーザを取得する。
レコードに存在しない場合は作成する。
app/models/user.rb
class User < ActiveRecord::Base
  # ...

  def self.find_for_oauth(auth)
    user = User.where(uid: auth.uid, provider: auth.provider).first

    unless user
      user = User.create(
        uid:      auth.uid,
        provider: auth.provider,
        email:    User.dummy_email(auth),
        password: Devise.friendly_token[0, 20]
      )
    end

    user
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end
end
メールアドレスでの認証も実装している場合、OAuthでの認証時もメールアドレスを保存する必要となる。
ここでは、uidproviderの組み合わせが一意なことを利用して、self.dummy_emailのように生成している。

参考

Userコントローラにコールバック処理を実装

app/controllers/usersディレクトリを作成し、omniauth_callbacks_controller.rbを作ります。
providerと同じ名前のメソッドを定義する必要がある。
ただ、基本的に各プロバイダでのコールバック処理は共通しているので、callback_fromメソッドに統一している。
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    callback_from :facebook
  end

  def twitter
    callback_from :twitter
  end

  private

  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_for_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to new_user_registration_url
    end
  end
end

ルーティング処理

以下のように、OAuthのコールバック用のルーティングを設定する。
config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks'
  }

  # ...
end

認証用リンクを追加

以下によりリンクが生成される。
これをビューの任意の位置に記述する。
user_omniauth_authorize_path(:facebook)
user_omniauth_authorize_path(:twitter)

まとめ



0 件のコメント:

コメントを投稿