Thiết lập gem Devise và OmniAuth trên ứng dụng Rails
Bài đăng này đã không được cập nhật trong 9 năm
Demo with facebook login: https://github.com/duongichi/study06
Bài viết này sẽ hướng dẫn các bạn có thể thiết lập chức năng sign up bằng mạng xã hội vào website.
Cài gem devise và omniauth
Trước hết mình cần phải cài đặt 2 gem là devise và omniauth.
Gemfile
gem 'devise', '~> 3.4'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'omniauth-instagram'
gem 'twitter'
gem 'instagram'
gem 'omniauth-google-oauth2'
gem 'google-api-client', require: 'google/api_client'
Tiếp đến bạn cài đặt devise
$ rails generate devise:install
Sau đấy là tạo model User
và cấu hình devise route
để sử dụng model này
$ rails generate devise User
Tiếp tục là tạo file views
$ rails generate devise:view
Devise
Test thử chức năng login của Devise
Tạo controller cở bản để check xem có login được ko
$ rails g controller welcome index
$ rake db:migrate
$ rails s
Chỉnh file routes.rb
:
get 'welcome/index'
root 'welcome#index'
Tiếp tục là thay đổi WeclomeController
để yêu cầu user authentication
:
class WelcomeController
before_action :authenticate_user!
def index
end
end
Bạn đừng quên button logout
: app/views/welcome/index.html.erb
<%= link_to "Signout", destroy_user_session_path, method: :delete %>
Cấu hình Omniauth###
Đầu tiên mình cần phải config service của omniauth
như sau
config.omniauth :google_oauth2, ENV['GOOGLE_OAUTH2_APP_ID'], ENV['GOOGLE_OAUTH2_APP_SECRET'], scope: "email,profile,offline", prompt: "consent"
config.omniauth :instagram, ENV['INSTAGRAM_APP_ID'], ENV['INSTAGRAM_APP_SECRET']
config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], scope: "email"
config.omniauth :twitter, ENV['TWITTER_APP_ID'], ENV['TWITTER_APP_SECRET']
Bạn cần phải cho các giá trị appid
và secret key
vào trong biến môi trường của server.
Ở đây thì giá trị truyền vào trong scope
sẽ là những giá trị mà mình lấy về được.
Kết nối Devise với omniauthable
Mở app/models/user.rb
và add :omniauthable
vào devise
và bỏ đi :validatable
:
devise :omniauthable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
Đây là list những cái sẽ connect với service của ban khi bạn ở trang sign in or login pages.
Tạo FormUser để xử lý validations
Không phải tất cả các service đều return lại email, nhưng mà devise validations
lại yêu cầu dạng email. Vì vậy nên mình sẽ di chuyển validations
từ class User
vào FormUser
.
ý
- bỏ
:validatable
khỏiapp/models/user.rb
- Config
devise
sử dụngmodel
mới của mình - Tạo class
forms_user.rb
config/routes.rb
devise_for :users, class_name: 'FormUser'
app/models/form_user.rb
class FormUser < User
attr_accessor :current_password
validates_presence_of :email, if: :email_required?
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: Devise.email_regexp, allow_blank: true, if: :email_changed?
validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: Devise.password_length, allow_blank: true
def password_required?
return false if email.blank?
!persisted? || !password.nil? || !password_confirmation.nil?
end
def email_required?
true
end
end
Tạo model Identity để lưu access_keys và metadata
Flow để kết nối với oauth authentications
:
- User sẽ yêu cầu
/users/auth/:provider
,provider
sẽ là 1 trong số những giá trị mình truyền vào trongscope
. Omniauth
chuyển hướng đến remote service.- User có thể access được và redirect về
callback path
- Gọi controller
OmniauthCallbacks
để lấy về những thông tin tương ứng.
Những thông tin này sẽ được sử dụng để tạo user
và truy cập. Ngoài ra thì mình cũng cần thiết phải lưu access_token
để có thể access vào service.
Đối với trường hợp Google thì phức tạp hơn 1 chút và bạn cần phải lưu cả refresh_token
nữa.
$ rails generate model identity user:references provider:string accesstoken:string refreshtoken:string uid:string name:string email:string nickname:string image:string phone:string urls:string
app/models/identity.rb
class Identity < ActiveRecord::Base
belongs_to :user
validates_presence_of :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_for_oauth(auth)
identity = find_by(provider: auth.provider, uid: auth.uid)
identity = create(uid: auth.uid, provider: auth.provider) if identity.nil?
identity.accesstoken = auth.credentials.token
identity.refreshtoken = auth.credentials.refresh_token
identity.name = auth.info.name
identity.email = auth.info.email
identity.nickname = auth.info.nickname
identity.image = auth.info.image
identity.phone = auth.info.phone
identity.urls = (auth.info.urls || "").to_json
identity.save
identity
end
end
Tiếp tục, mình cẩn phải ra lệnh cho devise
sử dụng model này.
Tạo OmniauthCallbacksController để kéo data về
Chúng ta sẽ tạo 1 method để xử lý những authentication
khác nhau khi callbacks, được gọi là generic_callback
. Logic của controller này sẽ như sau :
- Tạo object
Identity
cho nhữngoauth data
. Update với những thông tin mới nhất. - Nếu không có
user
nào liên kết vớiIdentity
thì liên kết nó vớicurrent_user
. - Nếu không có
current_user
thì tạo mới objectUser
. - Nếu object
current_user
không có email và mình get được 1 cái mail từ remote service thì sẽ set email đó cho nó. - Log in user đó. !!!
Route cho controller mới routes.rb
.
devise_for :users, class_name: 'FormUser', :controllers => { omniauth_callbacks: 'omniauth_callbacks' }
Tạo app/controllers/omniauth_callback_controller.rb
:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def instagram
generic_callback( 'instagram' )
end
def facebook
generic_callback( 'facebook' )
end
def twitter
generic_callback( 'twitter' )
end
def google_oauth2
generic_callback( 'google_oauth2' )
end
def generic_callback( provider )
@identity = Identity.find_for_oauth env["omniauth.auth"]
@user = @identity.user || current_user
if @user.nil?
@user = User.create( email: @identity.email || "" )
@identity.update_attribute( :user_id, @user.id )
end
if @user.email.blank? && @identity.email
@user.update_attribute( :email, @identity.email)
end
if @user.persisted?
@identity.update_attribute( :user_id, @user.id )
# This is because we've created the user manually, and Device expects a
# FormUser class (with the validations)
@user = FormUser.find @user.id
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: provider.capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
Override RegistrationsController để xử lý việc add thêm email address và password
Chúng ta sẽ add 1 email và thiết lập password cho user
.
Đầu tiên phải thiết lập route
cho devise
hiểu về controller mới.
devise_for :users, class_name: 'FormUser', :controllers => { omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'}
Tạo controller registrations
class RegistrationsController < Devise::RegistrationsController
def update_resource(resource, params)
if resource.encrypted_password.blank? # || params[:password].blank?
resource.email = params[:email] if params[:email]
if !params[:password].blank? && params[:password] == params[:password_confirmation]
logger.info "Updating password"
resource.password = params[:password]
resource.save
end
if resource.valid?
resource.update_without_password(params)
end
else
resource.update_with_password(params)
end
end
end
Tạo các method cho User để liên kết tới clients
app/models/user.rb
:
has_many :identities
def twitter
identities.where( :provider => "twitter" ).first
end
def twitter_client
@twitter_client ||= Twitter.client( access_token: twitter.accesstoken )
end
def facebook
identities.where( :provider => "facebook" ).first
end
def facebook_client
@facebook_client ||= Facebook.client( access_token: facebook.accesstoken )
end
def instagram
identities.where( :provider => "instagram" ).first
end
def instagram_client
@instagram_client ||= Instagram.client( access_token: instagram.accesstoken )
end
def google_oauth2
identities.where( :provider => "google_oauth2" ).first
end
def google_oauth2_client
if !@google_oauth2_client
@google_oauth2_client = Google::APIClient.new(:application_name => 'HappySeed App', :application_version => "1.0.0" )
@google_oauth2_client.authorization.update_token!({:access_token => google_oauth2.accesstoken, :refresh_token => google_oauth2.refreshtoken})
end
@google_oauth2_client
end
Kêt luận###
Enjoy your new login function !!! (hihi)
All rights reserved