0

login facebook, twitter sử dụng sorcery và những điều cần lưu ý

1. Giới thiệu

Tương tự như người anh em devise, sorcery là công cụ khá mạnh dùng trong authentication. Nhưng nó đơn giản hơn devise từ tính năng cho đến document 😄. Vì config của nó khá đơn giản, nếu chỉ dùng với các chức năng cơ bản thì như vậy đã là đủ, nhưng muốn mở rộng, phức tạp hơn một chút thì chúng ta phải customize nó một cách phức tạp hơn(vì nó không phổ biến như devise nên những vấn đề liên quan thì khó tìm kiếm hơn). Login facebook, twitter là chức năng được sử dụng khá phổ biến trong các website hiện nay, và trong bài viết này mình sẽ hướng dẫn về việc tạo chức năng này bằng gem sorcery.

2. Config gem

  • Việc đầu tiên khi sử dụng gem vẫn là add gem file và bundle. Lưu ý sử dụng phiên bản gem từ 0.11 trở lên
gem 'sorcery', '~> 0.11.0'

run: rails g sorcery:install external --only-submodules lệnh này sẽ tạo ra table

class SorceryExternal < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id, :null => false
      t.string :provider, :uid, :null => false
    
      t.timestamps
    end
  end
end

Tức là giờ đây, table user của bạn sẽ liên kết 1 nhiều với bảng authentications (đối với những thanh niên login bằng facebook thì sẽ tạo ra thêm 1 record lưu uid, provider

rake db:migrate

tạo model authentication rails g model Authentication --migration=false

Tiếp theo là đến file config của socery: Nguyên văn trong document sẽ là:

# config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:external, blabla, blablu, ...]

Rails.application.config.sorcery.configure do |config|
  ...
  config.external_providers = [:twitter, :facebook]

#add this file to .gitignore BEFORE putting any secret keys in here, or use a system like Figaro to abstract it!!! 
      
  config.twitter.key = "<your key here>"
  config.twitter.secret = "<your key here>"
  config.twitter.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=twitter"
  config.twitter.user_info_mapping = {:username => "screen_name"}
      
  config.facebook.key = "<your key here>"
  config.facebook.secret = "<your key here>"
  config.facebook.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=facebook"
  config.facebook.user_info_mapping = {:email => "email", :name => "name", :username => "username", :hometown => "hometown/name"} #etc
  config.facebook.scope = "email,offline_access,user_hometown,user_interests,user_likes" #etc
  config.facebook.display = "popup"
  ...

  # --- user config ---
  config.user_config do |user|
  ...

    # -- external --
    user.authentications_class = Authentication
    ...

  end
  ...
   
end
class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation, :authentications_attributes
  authenticates_with_sorcery! do |config|
    config.authentications_class = Authentication
  end

  has_many :authentications, :dependent => :destroy
  accepts_nested_attributes_for :authentications
end
# app/models/authentication.rb
class Authentication < ActiveRecord::Base
  attr_accessible :user_id, :provider, :uid
  belongs_to :user
end
# app/controllers/oauths_controller.rb
class OauthsController < ApplicationController
  skip_before_filter :require_login
      
  # sends the user on a trip to the provider,
  # and after authorizing there back to the callback url.
  def oauth
    login_at(params[:provider])
  end
      
  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
    else
      begin
        @user = create_from(provider)
        # NOTE: this is the place to add '@user.activate!' if you are using user_activation submodule

        reset_session # protect from session fixation attack
        auto_login(@user)
        redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
      rescue
        redirect_to root_path, :alert => "Failed to login from #{provider.titleize}!"
      end
    end
  end
  
  #example for Rails 4: add private method below and use "auth_params[:provider]" in place of 
  #"params[:provider] above.

  # private
  # def auth_params
  #   params.permit(:code, :provider)
  # end

end
  • Để hợp thời, chúng ta không sử dụng attr_accessible mà sẽ sử dụng strong params trong controller và viết lại như sau
module NowApi
  class OauthsController < ApiBaseController
    skip_before_action :require_valid_token

    def oauth
      login_at(params[:provider])
    end

    def callback
      provider = auth_params[:provider]
      ...
      end
    end

    private

    def auth_params
      params.permit(:code, :provider, authentications_attributes: [:uid, :provider])
    end
 end

Hiểu đơn giản thì khi tạo ra một user tương ứng với user facebook, sẽ tạo ra 1 record authentications tương ứng sử dụng nested_attributes

Nhưng một điều khá buồn, là nếu chỉ dùng nguyên config sorcery.rb này thì bạn sẽ không thể lấy được email, user_name bởi vì sorcery bắt bạn phải tự config tức là bạn cần thông tin gì từ user facebook, bạn phải include nó theo API hướng dẫn của facebook https://developers.facebook.com/tools/explorer/ bạn muốn lấy email, name, thậm chí là avatar thì bạn cần thêm dòng lệnh include như sau

config.facebook.user_info_path = "me?fields=email,name,picture.width(400).height(400),gender"

tại sao user_info_path lại là "me", cái này lấy ở đâu thì chỉ cần search code của gem tại lib/sorcery/providers/facebook.rb Nhờ include params như thế này mà chúng ta có thể lấy email, name, avatar từ facebook

Tương tự như vậy đối với twitter thì sẽ thêm config

config.twitter.user_info_path = '/1.1/account/verify_credentials.json?include_email=true'

tham khảo thêm về include trong api của twitter https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials

3. Kết luận

Việc login bằng facebook hay twitter trên socery khá là đơn giản, có thể là đơn giản hơn cả devise. Nhưng mức độ phổ biến của nó thì it hơn bởi vậy gặp những lỗi về config thì chúng ta sẽ khó tim được những câu hỏi và support tương tự. Điều lưu ý trên tuy không có gì ghê gớm, nhưng do document không ghi đầy đủ về việc include như thế nào nên sẽ rất khó khăn khi mới tiếp xúc sorcery. Chúc các bạn sử dụng gem thành công

4. Tài liệu tham khảo

https://github.com/Sorcery/sorcery https://github.com/NoamB/sorcery/wiki/External https://developer.twitter.com/en/docs/basics/authentication/overview/application-permission-model https://developers.facebook.com/tools/explorer/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí