[Rails] Devise 使用 Facebook 註冊與登入

Devise 使用 Facebook 註冊與登入

目的

  在 Devise 下,使用 Facebook 註冊與登入。

前言

  相信大家都很熟練 Devise 這個 Gem 的使用者機制,當然除了一般的註冊申請帳號外,最方便是要能與各大社交平台有所連結;而 Devise 中透過 OmniAuth 快速地串接 Facebook、google、github… 等,以下就筆記一下如何使用 Facebook 註冊登入!

開發版本與環境

1
2
3
  Ruby : v2.3.0
  Rails : v4.2.5
  IDE : Cloud 9

筆記

1. 什麼是 OmniAuth ?

  OmniAuth 是一個提供多方認證,且不被限定於特定框架的認證方案,主要架構如下:

Provider

  OmniAuth 將所有的認證方視為不同的 Provider,而每種 Provider 都有各自的 Strategy。

Strategy

  每個 Strategy 分為兩個 phase,對應 OmniAuth 提供的 url:

  • equest phase => /auth/:provider
  • callback phase => /auth/:provider/callback

  而它的流程是,透過 /auth/:provider 重導去認證,認證成功後 redirect 到 /auth/:provider/callback 作 session create 動作 ( 透過拿回來的資料 find_or_create user )

註: :provider 就是 facebook、github,所以 url 看起來會是 /auth/facebook、/auth/facebook/callback。

2. 一起來實作

步驟零、 Install gem

Gemfile
1
2
3
gem 'omniauth'
gem 'omniauth-facebook'
gem 'koala', "~-> 2.2" # 為了讓授權失敗時,刪除 permission session 使用

步驟一、 建立 Facebook Application

  使用 Facebook 應用服務前,需先到 Facebook developer 建立應用程式,只要按照步驟就可以順利建立完成。

步驟二、 於 devise.rb 中設定 config.omniauth

devise.rb
1
2
3
4
5
config.omniauth :facebook, ENB['FB_KEY'], ENV['FB_SECRET'],
scope: 'email',
info_fields: 'email, name, link',
secure_image_url: true,
image_size: 'large'

這邊使用 Rails_ENV 確保我們的資料隱密性,可參考 這篇的 Rails_ENV 小筆記

  • FB_KEY:Facebook 應用程式編號
  • FB_SECRET:Facebook 應用程式密鑰
  • scope:表示將取得使用者的什麼權限,email 為預設,即取得使用者公開的資訊 ( public_profile ) 權限
  • info_fields:表示在擁有權限下,取得使用者的什麼資訊,這邊我們取得使用者的 Email、名稱和 FB 連結
  • secure_image_url:為 true,表示我們透過 https 取得使用者大頭貼
  • image_size:此為取得大頭貼的 size,square(50x50)、small(50 px width, ariable height)、normal(100 px width, variable height)、large(約 200 px wide, variable height)

以上設定,我們可以從 omniauth-facebook 清楚的了解。
若是想取得其他的權限與資訊,可以先到 Facebook developer permissions 查看。

步驟三、 於 user.rb 中設定 omniauth

user.rb
1
devise :omniauthable, :omniauth_providers => [:facebook]

  定義認證後的創建行為,會在取得使用者 Facebook 權限與資訊後執行:

user.rb
1
2
3
4
5
6
7
8
9
10
11
12
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.name = auth.info.name
user.facebook = auth.info.urls.Facebook
user.password = Devise.friendly_token[0,20]
user.remote_avatar_url = auth.info.image
user.skip_confirmation! # 如果 devise 有使用 confirmable,記得 skip!
end
end

  授權失敗時,透過 koala 刪除使用者授權的 session,以便重新授權。

user.rb
1
2
3
4
def delete_access_token(auth)
@graph ||= Koala::Facebook::API.new(auth.credentials.token)
@graph.delete_connections(auth.uid, "permissions")
end

步驟四、 定義 omniauth callback controller

  新增一個 Users::OmniauthCallBacksController,我們在這個地方定義兩個 action:facebook 和 failure。

  • facebook method 作的三件事:

   1. 使用者授權後,依取得的資訊創建新的 user。
   2. 如果此使用者已經存在網站中,直接登入。
   3. 授權後,未取得 email 者,重新要求授權。(這是因為我的網站使用者 email 為必填項目,且為登入憑據)

  • failure:重新導向註冊頁,並告知使用者授權錯誤。
omniauth_callback.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
@user = User.from_omniauth(request.env["omniauth.auth"]) # 這邊會呼叫我們在 user 中定義的創建行為
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
if @user.email.nil?
# 如果 User 沒有提供 email 刪除 User 的 app 讓 User 重新授權
@user.delete_access_token(request.env["omniauth.auth"])
redirect_to new_user_registration_url, alert: "需要您同意 Email 授權唷!"
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
def failure
redirect_to new_user_session_path, alert: "無法獲得驗證!"
end
end

步驟五、 到 routes.rb 設定 devise omniauth callback 該用哪個 controller

  我們要讓 omniauth callback 時使用我們定義的 controller:

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

Devise 使用 OmniAuth 時,會自動幫你在 registrations#new 產生 provider 登入的 link,所以不必自行添加,或是你也可以在想添加的地方加入:
  <%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>

後記

  授權失敗時,需要重新授權 (Permissions revoke),這個問題看似很簡單,卻花了我不少時間;原因是不想為了解決這個小問題,而多使用一個 gem,但最終我還是選擇用 koala 解決了,如果有不多加使用 gem 的解法請來信或留言,感激不盡!

That’s it, DONE!

【參考資料】