Thiết lập gem Devise và OmniAuth trên ứng dụng Rails
Nguồn : http://willschenk.com/setting-up-devise-with-twitter-and-facebook-and-other-omniauth-schemes-without-email-addresses/ 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 ...
Nguồn : http://willschenk.com/setting-up-devise-with-twitter-and-facebook-and-other-omniauth-schemes-without-email-addresses/
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ỏi app/models/user.rb
- Config devise sử dụng model 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 trong scope.
- 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ững oauth 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ới Identity thì liên kết nó với current_user.
- Nếu không có current_user thì tạo mới object User.
- 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)