#はじめに
現在Raisを使用しアプリケーションの作成を行なっています。今回は既に実装しているログイン機能にSNSアカウントでログインできるよう追加実装していきます。備忘録及び復習のため記述していきます。
#環境
Ruby on Rails '6.0.0'
Ruby '2.6.5'
#前提
- devise(gem)を使用し、ユーザーのログイン機能実装済み。
- facebook for developersと Google Cloud Platformから外部APIの設定済み。
#①Gemの導入と環境変数の設定
omniauth
・Google,Facebook,Twitter等のSNSアカウントを用いてユーザー登録やログインなどを実装できるgemです。今回はFacebookとGoogleのomniauthをインストールします。
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
% bundle install
また、外部API設定時に取得したIDやシークレットキーを環境変数に設定します。環境変数の設定方法はいくつかあると思いますが、私の場合は、「gem 'dotenv-rails'」により「.envファイル」を設定済みのため、そこに記述しました。
FACEBOOK_CLIENT_ID='アプリID'
FACEBOOK_CLIENT_SECRET='app secret'
GOOGLE_CLIENT_ID='クライアントID'
GOOGLE_CLIENT_SECRET='クライアントシークレット'
次にアプリケーション側で環境変数を読み込む記述をします。
(省略)
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
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」にてデータベースに反映します。
アソシエーションの設定をします。
(省略)
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を使用できます。
class SnsCredential < ApplicationRecord
belongs_to :user
end
#③deviseの再設定
deviseのコントローラーを再設定するため、コントローラーを作成しています。
% rails g devise:controllers users
生成したコントローラーを使用させるために、deviseのルーティングを変更します。
Rails.application.routes.draw do
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations',
sessions: 'users/sessions',
}
・・・(省略)・・・
end
#④コントローラーの設定
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ファイルの編集
(省略)
<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を.(ドット)で繋いで定義します。
(省略)
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認証を行なったかどうか確認した後のコードを記述します。
(省略)
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モデルから返ってきた後の処理を記述します。
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モデルの編集
(省略)
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ファイルも編集して完了です!!
#終わりに
もしこの記事を参考に、実装してみて、うまくできない場合は教えてください。。初めての実装のためお許しください。。