Just do IT

思うは招く

omniauthを使ってRailsアプリにFacebook認証を実装する

前回の記事の続き。

k-koh.hatenablog.com

やりたいこと

Facebookログインができるようになりたい。

f:id:K_Koh:20200225144342j:plain

ここから

f:id:K_Koh:20200225144330p:plain

こんな感じでログインできるようにする。 (Facebookに登録している名前とメールアドレスが表示されている)

環境

  • Rails 6.0.2.1
  • ruby 2.6.5
  • MacOS/Homebrew
  • deviseでログイン機能が実装されていること

Facebookアプリの用意

まず、Facebook for DevelopersFacebookアプリを作成し、「アプリID」と「シークレットキー」を取得する。

Facebookにログインしている状態で、「アプリの作成」へ。

f:id:K_Koh:20200225110013j:plain

表示名に適当にアプリの名前を入れる。

f:id:K_Koh:20200225110103p:plain

Facebookログイン」の設定へ。

f:id:K_Koh:20200225110614p:plain

「ウェブ」をクリック。

f:id:K_Koh:20200225110739p:plain

URLにはhttp://localhost:3000/を入力。

f:id:K_Koh:20200225110818p:plain

saveし、あとはすべて「次へ」を押す。

次に左カラムにあるFacebookの設定へ。

f:id:K_Koh:20200225111001j:plain

「リダイレクトURI検証ツール」にコールバック用URLを入力し、「URLをチェック」をクリックしてから保存。

https://localhost:3000/users/auth/facebook/callback

f:id:K_Koh:20200225111116j:plain

アプリIDとシークレットキーをコピーしておくか、この画面を開いておく。

f:id:K_Koh:20200225111334p:plain

これでFacebook側の準備は終了。

Rails側の実装

次はRailsをいじっていく。

必要なgemをインストール

今回使うgemは以下のとおり。

omniauthはSNS認証機能を提供するgemの大本で、omniauth-facebookやomniauth-twitterなどといった、補足的なgemを組み合わせて使う。 これらのgemとdevise gemがうまいこと連携してくれる。

Gemfileに以下を記述。

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # 追記
  gem 'dotenv-rails'
end

~
~

# OAuth
gem 'omniauth'
gem 'omniauth-facebook'

インストール。

bundle

.envファイルを作成

.envを作成。

touch .env

以下のようにアプリID、シークレットキーを記述。 (番号は伏せている)

FACEBOOK_APP_ID=1111111111111
FACEBOOK_APP_SECRET=1111111111111111

dotenv-railsは、.envというファイルを作ったら、そこに書かれた環境変数を読み込んでくれるgem。 つまり、ここで書いたFACEBOOK_APP_IDFACEBOOK_APP_SECRETを参照してくれる。

devise.rbに追記

config/initializers/devise.rbに以下を追記

  # ==> OmniAuth
  # Add a new OmniAuth provider. Check the wiki for more information on setting
  # up on your models and hooks.
  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'

  # 追記
  config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET']

こうすることで、dotenv-rails.envに書かれたFACEBOOK_APP_IDFACEBOOK_APP_SECRETを、ENV['FACEBOOK_APP_ID']ENV['FACEBOOK_APP_SECRET']として読み込んでくれる。

deviseの設定ファイルに書くことで、deviseに「omniauthを使ってFacebookのアプリIDとシークレットキー読み込みたいからよろしくー」と頼んでいる。

Userモデルにカラムを追加

SNS認証を実装する際、ユーザー情報を保存するモデルには、providerとuidカラムを保存する。 providerにはサービス名、uidはサービスごとのユーザーidが保存される。

  • provider: SNS認証をするサービス名(今回はFacebook
  • uid: ユーザーid

これは決まりごとなので、あまり考えずにさっと作ってしまう。

カラムを追加するため、マイグレーションファイルを作成する。SNSで登録しているユーザー名も保存したいので、今回はnameカラムも作成している。すべてstring型でOK。

bin/rails g migration AddColumnsToUsers name:string provider:string uid:string

マイグレーションを実行。

bin/rails db:migrate

カラムを見てみると。

db/schema.rb

ActiveRecord::Schema.define(version: 2020_02_24_074611) do

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "name"
    t.string "provider"
    t.string "uid"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

end

name, provider, uidカラムができている。

コントローラーを作成

omniauthとdeivseを連携させるため、deviseコマンドを使ってコントローラーを作成する。 ここではusersテーブルに合わせて、usersコントローラーを作る。

bin/rails g devise:controllers users

すると「ルーティングやってね」というメッセージが出てくる。

  For example:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end

なのでルートの設定をする。

routes.rb

Rails.application.routes.draw do
  get 'home/index'
  devise_for :users, controllers: {
    omniauth_callbacks: "users/omniauth_callbacks",
    registrations: "users/registrations",
  }
  root to: 'home#index'
end

SNS認証をしたときのコールバック処理を定義する

class Users::OmniAuthCallbacksController < Devise::OmniAuthCallbacksController
  def facebook
    callback
  end

  private

    def callback
      @user = User.find_or_create_for_oauth(request.env['omniauth.auth'])

      if @user.persisted?
        sign_in_and_redirect @user
      else
        session['devise.user_attributes'] = @user.attributes
        redirect_to new_user_registration_url
      end
    end
end

Userモデルの修正

app/models/user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: [:facebook] # 追記
end

deviseの機能であるomniauthableモジュールを使うことによって、omniauth認証が可能になる。

さらにオプションomniauth_providers: [:facebook]をつけることで、プロバイダー、すなわち認証に使用するSNS名を指定している。 validatableの後ろに,をつけ忘れないように!

さらに以下を追記。

app/models/user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: [:facebook]

  # 追記
  class << self
    def find_or_create_for_oauth(auth)
      find_or_create_by!(email: auth.info.email) do |user|
        user.provider = auth.provider
        user.uid = auth.uid
        user.name = auth.info.name
        user.email = auth.info.email
        password = Devise.friendly_token[0..5]
        logger.debug password
        user.password = password
      end
    end

    # 追記
    def new_with_session(params, session)
      if user_attributes = session['devise.user_attributes']
        new(user_attributes) { |user| user.attributes = params }
      else
        super
      end
    end
  end
end

viewを修正

ログイン後、emailとnameを表示させるようにする。 current_user.emailで認証したSNSで登録に使用したemailアドレスを取得できる。

app/views/home/index.html.erb

<h1>Home#index</h1>

<ul>
  <li><%= current_user.email %></li>
  <li><%= current_user.name %></li>
</ul>

サーバーを起動

サーバーを起動させてFacebookログインができるか確認してみる。

bin/rails s

f:id:K_Koh:20200225144342j:plain

sign in with Facebookをクリック。

f:id:K_Koh:20200225144330p:plain

Facebookに登録しているメールアドレスと名前が表示されているはず。

参考