透過OmniAuth使用FB帳號登入

Rails 設定

這邊會使用到devise(使用者登入) , figaro(帳號管理) , omniauth-facebook 這三個gem來實作這個功能。
devise跟figaro在這邊就不會詳細說明。

在gemfile加入 omniauth-facebook

gemfile.rb
gem `omniauth-facebook`

然後bundle install

設定devise config

config/initializers/devise.rb
-  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'

+  config.omniauth :facebook, 'APP_ID', 'APP_SECRET', scope: 'email, publish_actions'
     
#用figaro的功能的話就可以把APP_ID&APP_SECRET寫在application.yml裡,程式就變成

#config.omniauth :facebook, ENV["fb_app_id"], ENV["fb_app_key"], scope: 'email,publish_actions'

APP_ID&APP_SECRET是申請fb應用程式時會有的。

幫user model新增需要欄位

$ rails g migration AddOmniauthToUsers provider:string uid:string
$ rake db:migrate

設定user model的devise參數

在user model裡的devise的參數裡加入omniauthable &omniauth_providers讓devise可以透過omniauth登入。

app/model/user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable,:omniauth_providers => [:facebook]

加入fb登入按鈕

<%= link_to "Facebook 登入", user_omniauth_authorize_path(:facebook) %>

設定route可以讓fb登入時轉向對應到的controller

routes.rb
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

在user model新增from_omniauth

app/model/user.rb
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
    end
  end

新增devise的omniauth_callbacks_controller

rails g devise:controllers users

會在app/controllers裡建立一個users資料夾,並在裡面新增所以devise會用到的controller,包括omniauth_callbacks_controller。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  
  def failure
    super
  end

  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
end

完成!

用fb登入的帳號略過寄認證信直接登入

如果devise有開啟confirmable功能的話,用fb登入的話可以用下面的方式略過寄認證信然後直接登入。

app/controllers/users/omniauth_callbacks_controller.rb
def facebook
    @user = User.from_omniauth(request.env["omniauth.auth"])
    if @user.persisted?
        @user.skip_confirmation!#加入這一行就可以略過認證信

      sign_in_and_redirect @user , :event => :authentication
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
      (略)

程式說明

幫user model新增需要欄位

我們需要幫user model新增provider(用來記住第三方登入來源)&uid(第三方所提供的unique id)欄位,如果需要紀錄使用者的其他資訊的話就再自己新增例如name或是user photos。

設定devise config

scop的參數是代表這個app需要跟FB要哪種權限(需要取得使用者的什麼資料)。
詳細的權限可以參考這邊facebook-login-permissions

在user model新稱from_omniauth

app/model/user.rb
  def self.from_omniauth(auth)
  #method前面加self是要把method定義成class method

  #這樣不用實例化出來就可以使用該class的method

    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
      #用 Devise.friendly_token建一個密碼,這邊是建一個20個字的密碼。

    end
  end

first_or_create 查詢有沒有符合條件的資料,沒有的話就初始化並且存進資料庫。
參考資料:first_or_initialize 和 first_or_create

新增devise的omniauth_callbacks_controller

app/controllers/users/omniauth_callbacks_controller.rb
  def facebook
      @user = User.from_omniauth(request.env["omniauth.auth"])
      if @user.persisted?
      #persisted? 判斷是否已經存入 database 且不是新增的資料。

      #如果已經有此筆user資料的話就登入並導向。

        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

auth撈到的資料內容


       "provider" => "facebook",
            "uid" => "1xxxxx",
           "info" => {
        "email" => "xxxxx@gmail.com",
         "name" => "臉書的名稱",
        "image" => "http://graph.facebook.com/xxxxxxxxxx/picture"
    },
    "credentials" => {
             "token" => "xxxxxx",
        "expires_at" => xxxxxx,
           "expires" => true
    },
          "extra" => {
        "raw_info" => {
             "name" => "臉書的名稱",
            "email" => "xxxx@gmail.com",
               "id" => "1020xxxxxx921"
        }
    }
}

參考資料:persisted? vs new_record?
growthschool
skipping email confirmation for omniauth users using devise
Rails4でOmniAuthを使用したFacebookログイン機能を実装する
OmniAuth Facebook

comments powered by Disqus