LoginSignup
3
2

More than 3 years have passed since last update.

【Rails6】SNS認証による新規登録機能実装(Facebook, Google)

Posted at

はじめに

現在Raisを使用しアプリケーションの作成を行なっています。今回は既に実装しているログイン機能にSNSアカウントでログインできるよう追加実装していきます。備忘録及び復習のため記述していきます。

環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'

前提

①Gemの導入と環境変数の設定

omniauth
・Google,Facebook,Twitter等のSNSアカウントを用いてユーザー登録やログインなどを実装できるgemです。今回はFacebookとGoogleのomniauthをインストールします。

Gemfile
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
ターミナル
% bundle install

また、外部API設定時に取得したIDやシークレットキーを環境変数に設定します。環境変数の設定方法はいくつかあると思いますが、私の場合は、「gem 'dotenv-rails'」により「.envファイル」を設定済みのため、そこに記述しました。

.env
FACEBOOK_CLIENT_ID='アプリID'
FACEBOOK_CLIENT_SECRET='app secret'
GOOGLE_CLIENT_ID='クライアントID'
GOOGLE_CLIENT_SECRET='クライアントシークレット'

次にアプリケーション側で環境変数を読み込む記述をします。

config/initializers/devise.rb
(省略)
  config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
  config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']

②モデルの設定

SNSのWebAPIにリクエストを送ると、API上で認証を行います。その後、SNS上に登録されている情報がアプリケーション側に返される仕組みです。
APIからのレスポンスの中の、「uid」と「provider」をアプリケーションのデータベースに、ユーザー情報(名前など)と共に保存します。
しかし、「SNS認証とユーザー登録のタイミングが異なる」仕様であるため、SNS認証時にはusersテーブルのレコードを作成することはできません。そこでSNS認証時の情報を保存する別テーブル(SnsCredentialモデル)を作成しました。

ターミナル
% rails g model sns_credential
db/migrate/XXXXXXXXXXXXXXXX_create_sns_credentials.rb
class CreateSnsCredentials < ActiveRecord::Migration[6.0]
 def change
   create_table :sns_credentials do |t|
     t.string :provider
     t.string :uid
     t.references :user,  foreign_key: true

     t.timestamps
   end
 end
end

userモデルとのアソシエーションのため、外部キーとしてuser_idを持たせています。編集がおわり次第、「rails db:migrate」にてデータベースに反映します。

アソシエーションの設定をします。

app/models/user.rb
(省略)
has_many :sns_credentials, dependent: :destroy

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

omniauth_providers: [:facebook, :google_oauth2]と記述することで、FacebookとGoogleのOumniAuthを使用できます。

app/models/sns_credential.rb
class SnsCredential < ApplicationRecord
  belongs_to :user
end

③deviseの再設定

deviseのコントローラーを再設定するため、コントローラーを作成しています。

ターミナル
% rails g devise:controllers users

生成したコントローラーを使用させるために、deviseのルーティングを変更します。

config/routes
Rails.application.routes.draw do
  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations',
    sessions: 'users/sessions',
  }
・・・(省略)・・・
end

④コントローラーの設定

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env["omniauth.auth"]
  end
end

プライベートメソッド内に記述している「authorization」を呼び出す「facebook」と「google_oauth2」というアクションを定義します。

⑤Viewファイルの編集

app/views/users/registrations/new.html.erb
(省略)
<div class="d-flex justify-content-between mt-4">
  <%= link_to user_facebook_omniauth_authorize_path, class:"btn btn-outline-primary sns-btn", method: :post do %>
    <i class="fab fa-facebook fa-2x"></i>
  <% end %>
  <%= link_to user_google_oauth2_omniauth_authorize_path, class:"btn btn-outline-danger sns-btn", method: :post do %>
    <i class="fab fa-google fa-2x"></i>
  <% end %>
</div>

⑥modelの編集

Userモデルに入るデータのため、Userモデルにクラスメソッドを作成しました。
クラスメソッドとは、クラスで共通の情報を使った処理に使用するようです。記述方法は、メソッド名の前にselfを.(ドット)で繋いで定義します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
 end

上記でomniauth_callbacks_controller.rbに記述した、User.from_omniauthも呼び出せるようになりました。
また、取得した情報内の「provider」と「uid」をsnsに代入して、first_or_createメソッドを用いています。
(first_or_createメソッド:保存するレコードがデータベースに存在するか検索を行い、検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを保存するメソッドです。)

次に、SNS認証を行なったかどうか確認した後のコードを記述します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )
 end

上記で追加したコードは、SNS認証を行なっていなかった場合、メールアドレスで検索をします。first_or_initializeを用いて、名前とメールアドレスを返します。
(first_or_initialize:whereメソッドとともに使うことで、whereで検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを作るメソッドです。)

first_or_create:新規レコードをデータベースに保存する
first_or_initialize:新規レコードをデータベースに保存しない

⑦controllerの編集

Userモデルから返ってきた後の処理を記述します。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted? #ユーザー情報が登録済みのため、新規登録ではなくログイン処理を行う
      sign_in_and_redirect @user, event: :authentication
    else #ユーザー情報が未登録のため、新規登録画面へ遷移する
      render template: 'devise/registrations/new'
    end
  end
end

Userモデルから返ってきた値を@userに代入します。これは、viewで取得した「名前」と「メールアドレス」を表示させるためです。

次に、ログイン時の記述を行います。

⑧Userモデルの編集

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )

  # userが登録済みであるか判断
   if user.persisted?
     sns.user = user
     sns.save
   end
   user
 end

persisted?で登録済みのユーザーと判断を行い、「if文」の中が呼ばれます。新規登録時は、SnsCredentialモデルが保存されるタイミングで、user_idが確定していなかったので、SnsCredentialモデルとUserモデルは紐づいていません。ログインの際に、sns.userを更新して紐付けを行います。

最後にログイン画面のviewファイルも編集して完了です!!

終わりに

もしこの記事を参考に、実装してみて、うまくできない場合は教えてください。。初めての実装のためお許しください。。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2