Passion make things more better

Ruby on Rails / React.js / Swift / AWS / Docker

sorceryでFacebookログインを実装する

個人的に最近deviceではなくsorceryを使うことが多くなりました。deviceと比較してsorceryの良い点として、必要な機能だけを使えるという所です。

今回はsorceryを使ってFacebookログインを実装する方法を紹介します。

初期設定

# Gemfile
gem 'sorcery'

# bundle install
~ bundle install --path vendor/bundle

# sorceryの初期ファイルのインストール
~ bundle exec rails g sorcery:install

# Facebookログインを実装するためのファイル等インストール
~ bundle exec rails g sorcery:install external --only-submodules

# Authentication Model作成
~ bundle exec rails g model Authentication --migration=false

設定ファイル

# config/routes.rb
post 'logout', to: 'users/sessions#destroy', as: 'logout'
get 'oauth/callback', to: 'oauth#callback'
get 'oauth/:provider', to: 'oauth#oauth', as: 'auth_at_provider'
# config/initializers/sorcery.rbの該当部分に以下を追記

Rails.application.config.sorcery.configure do |config|

  config.external_providers = [:facebook]

  config.facebook.key = Settings.facebook[:key]
  config.facebook.secret = Settings.facebook[:secret]
  config.facebook.callback_url = Settings.facebook[:callback_url]
  config.facebook.display = "page"
  # 以下3つは取得したい項目と合わせて変更する
  config.facebook.user_info_mapping = { email: "email", name: "name", gender: "gender" }
  config.facebook.user_info_path = "me?fields=email,name,gender"
  config.facebook.access_permissions = ["public_profile", "email"]
  
  
  config.user_config do |user|
    user.authentications_class = Authentication
  end

end

Migration

# db/migrate/xxxxxxxxxxx_sorcery_core.rb
class SorceryCore < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, null: false
      
      t.timestamps
    end

    add_index :users, :email, unique: true
  end
end
# db/migrate/xxxxxxxxxxx_sorcery_external.rb
class SorceryExternal < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, null: false
      t.string :uid, null: false

      t.timestamps
    end

    add_index :authentications, [:provider, :uid], unique: true, name: 'ui_authentications_01'
  end
end

Model

# app/model/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery! do |config|
    config.authentications_class = Authentication
  end

  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications

  validates :email, presence: true, uniqueness: true
end
# app/model/authentication.rb
class Authentication < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
  validates :provider, uniqueness: { scope: :uid }
end

Controller

# app/controllers/app_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :require_login
  # もしログインを必要としない場合は以下を該当のControllerに記述する
  # skip_before_action :require_login

  private
  def not_authenticated
    redirect_to login_path, alert: "ログインが必要です"
  end
end
# app/controllers/users/sessions_controller.rb
class Users::SessionsController < ApplicationController
  def destroy
    logout
    redirect_to root_path, notice: 'ログアウトしました'
  end
end
# app/controllers/oauth_controller.rb
class OauthController < ApplicationController
  skip_before_action :require_login

  def oauth
    login_at(params[:provider])
  end

  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, notice: 'ログインに成功しました'
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)

        redirect_to root_path, notice: 'ログインに成功しました'
      rescue
        redirect_to root_path, alert: 'ログインに失敗しました'
      end
    end
  end

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

View

# ログイン配置したいところに以下のコードを記述
<%= link_to 'Login with Facebook', auth_at_provider_path(provider: :facebook) %>

参考