##この記事で解決できること
deviseを使ったSNS認証ログイン
snsのユーザ情報を利用して登録フォームに入力する
snsを使って登録するとき(ニックネームとメールアドレスはsnsの情報から自動で入力される)
##開発環境
MacOS Mojave 10.14.6
ruby 2.5.1
Rails 5.2.3
mysql Ver 14.14 Distrib 5.6.43, for osx10.14
deviseでの新規登録ができる前提で進めます
##実装手順
下記の記事で通常の登録を解説してます。こちらを読んでからのほうが流れが理解しやすいかもしれないです...
1 必要なgemを導入
2 devise.rbを変更
3 ルーティングを設定
4 sns情報を保存するためのテーブルを作る
4 user.rbを変更
5 コントローラーで処理を書く
###gemを導入
gem 'devise'
gem 'omniauth-rails_csrf_protection' #omniauthでもいいがセキュリティー的にこっちのほうが安全らしい
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
###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'],scope: 'email'
環境変数の設定方法やAPI_KEYの取得方法は下記の記事を参考にしてください
[Rails] Facebook/Twitter/Googleでのユーザー登録をDevise & Omniauthを使って爆速で実装する
###ルーティングを設定
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
###sns情報を保存するためのテーブルを作る
rails g modle sns_credential provider:string uid:string user:references
class SnsCredential < ApplicationRecord
belongs_to :user
end
###user.rbを変更
class User < ApplicationRecord
devise :omniauthable, omniauth_providers: %i[facebook google_oauth2] #このオプションを追加
has_many :sns_credentials, dependent: :destroy
#validatesは省略
#omniauth_callbacks_controllerで呼び出すメソッド
def self.find_oauth(auth)
uid = auth.uid
provider = auth.provider
snscredential = SnsCredential.where(uid: uid, provider: provider).first #firstをつけないとデータが配列で返されて使いたいメソッドが使えなくて困る
#sns_credentialsが登録されている
if snscredential.present?
user = User.where(email: auth.info.email).first
# userが登録されていない
unless user.present?
user = User.new(
nickname: auth.info.name,
email: auth.info.email,
)
end
sns = snscredential
#返り値をハッシュにして扱いやすくする
#活用例 info = User.find_oauth(auth)
#session[:nickname] = info[:user][:nickname]
{ user: user, sns: sns}
#sns_credentialsが登録されていない
else
user = User.where(email: auth.info.email).first
# userが登録されている
if user.present?
sns = SnsCredential.create(
uid: uid,
provider: provider,
user_id: user.id
)
{ user: user, sns: sns}
# userが登録されていない
else
user = User.new(
nickname: auth.info.name,
email: auth.info.email,
)
sns = SnsCredential.new(
uid: uid,
provider: provider
)
{ user: user, sns: sns}
end
end
end
end
###コントローラーで処理を書く
deviseのコマンドでコントローラーを作るときに自動で作成される。ない場合は作成してください。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# callback for facebook
def facebook
callback_for(:facebook)
end
# callback for google
def google_oauth2
callback_for(:google)
end
# common callback method
def callback_for(provider)
info = User.find_oauth(request.env["omniauth.auth"])
# snsの情報からuserが登録されているか or snsから情報を取得できているかを確認
@user = User.where(nickname: info[:user][:nickname]).or(User.where(email: info[:user][:email])).first || info[:user]
# persisted?はデータがDBに保存されているかを確認する/配列に対しては使えないから@userを定義するときは気をつける
if @user.persisted?
#保存されていればログインしてroot_pathにリダイレクトされる
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
# 登録するアクションに取得した値を渡すために。sessionを利用してuserインスタンスを作成する
session[:nickname] = info[:user][:nickname]
session[:email] = info[:user][:email]
#snsでのユーザ登録ではパスワードを入力させないので準備する。パスワードを作成するDeviseメソッドもある。
#今回のバリデーションは英数字のみなのでこっちを採用
session[:password_confirmation] = SecureRandom.alphanumeric(30)
#SnsCredentialが登録されていないとき
if SnsCredential.find_by(uid: info[:sns][:uid], provider: info[:sns][:provider]).nil?
#ユーザ登録と同時にsns_credentialも登録するために
session[:uid] = info[:sns][:uid]
session[:provider] = info[:sns][:provider]
end
#登録フォームのviewにリダイレクトさせる
redirect_to step1_signups_path
end
end
def failure
redirect_to root_path
end
end
###リダイレクト先のコントローラー
class SignupsController < ApplicationController
before_action :authenticate_user!, only: :done
def index
delete_session # ユーザ登録を途中でやめたときのsessionを削除するために
redirect_to root_path if user_signed_in?
end
#omniauth_callbacks_controllerからリダイレクトされたアクション
def step1
#sns認証を使った場合は情報利用してインスタンスを作成.
#viewで条件分岐などを利用してパスワードフォームを表示させない
@user = if session[:password_confirmation]
User.new(
nickname: session[:nickname],
email: session[:email],
password_confirmation: session[:password_confirmation]
)
else
User.new
end
end
def step1_validates
# バリデーションチャックのためにインスタンスを作成
@user = if session[:password_confirmation].present?
set_user_when_sns(user_params)
else
set_user_when_email(user_params)
end
@user.valid?
skip_phonenumber_validate(@user.errors)
if verify_recaptcha(model: @user, message: "選択してください") && @user.errors.messages.blank? && @user.errors.details.blank?
create_session(user_params) ##createにわたすためにsessionを作成
redirect_to step2_signups_path
else
@user.errors.messages[:birthday_day] = change_birthday_validate_message(@user)
render :step1
end
end
def step2
redirect_to signups_path if session[:lastname].blank? ##URLの直うちアクセスを防ぐため
@user = User.new
end
def create
set_user_with_session
@user[:phonenumber] = user_params[:phonenumber]
if @user.save
SnsCredential.create( #ユーザ登録と同時にこっちも登録
uid: session[:uid],
provider: session[:provider],
user_id: @user.id
)
sign_in User.find(@user.id) unless user_signed_in?
redirect_to addresses_path
else
render :step2
end
end
def done; end
private
def user_params
params.require(:user).permit(:nickname, :email, :password, :password_confirmation, :lastname, :firstname,
:lastname_kana, :firstname_kana, :birthday_year,
:birthday_month, :birthday_day, :phonenumber)
end
def create_session(user_params)
session[:nickname] = user_params[:nickname]
session[:email] = user_params[:email]
# sns認証のときはsessionを使っている
if session[:password_confirmation]
session[:password] = session[:password_confirmation]
else
session[:password] = user_params[:password_confirmation]
session[:password_confirmation] = user_params[:password_confirmation]
end
session[:lastname] = user_params[:lastname]
session[:firstname] = user_params[:firstname]
session[:lastname_kana] = user_params[:lastname_kana]
session[:firstname_kana] = user_params[:firstname_kana]
session[:birthday_year] = user_params[:birthday_year]
session[:birthday_month] = user_params[:birthday_month]
session[:birthday_day] = user_params[:birthday_day]
end
def set_user_with_session
@user = User.new(
nickname: session[:nickname],
email: session[:email],
password: session[:password_confirmation],
password_confirmation: session[:password_confirmation],
lastname: session[:lastname],
firstname: session[:firstname],
lastname_kana: session[:lastname_kana],
firstname_kana: session[:firstname_kana],
birthday_year: session[:birthday_year],
birthday_month: session[:birthday_month],
birthday_day: session[:birthday_day]
)
end
def set_user_when_sns(user_params)
session[:email] = user_params[:email] unless session[:email] ##sns認証でメールがない場合sessionが作られないので
User.new(
##omniauth_callbacks_controller#callback_forで作成したsessionを利用
nickname: session[:nickname],
email: session[:email],
password: session[:password_confirmation],
password_confirmation: session[:password_confirmation],
lastname: user_params[:lastname],
firstname: user_params[:firstname],
lastname_kana: user_params[:lastname_kana],
firstname_kana: user_params[:firstname_kana],
birthday_year: user_params[:birthday_year],
birthday_month: user_params[:birthday_month],
birthday_day: user_params[:birthday_day]
)
end
def set_user_when_email(user_params)
User.new(
nickname: user_params[:nickname],
email: user_params[:email],
password: user_params[:password_confirmation],
password_confirmation: user_params[:password_confirmation],
lastname: user_params[:lastname],
firstname: user_params[:firstname],
lastname_kana: user_params[:lastname_kana],
firstname_kana: user_params[:firstname_kana],
birthday_year: user_params[:birthday_year],
birthday_month: user_params[:birthday_month],
birthday_day: user_params[:birthday_day]
)
end
# 電話番号をstep1で入力しないので空のときのバリデーションをスキップする
def skip_phonenumber_validate(errors)
errors.messages.delete(:phonenumber)
errors.details.delete(:phonenumber)
end
# 生年月日のどれかにひとつでもバリデーションエラーがあった場合は同じエラーメッセージを表示する
def change_birthday_validate_message(user)
if user.errors.messages[:birthday_year].any? || user.errors.messages[:birthday_month].any? || user.errors.messages[:birthday_day].any?
user.errors.messages.delete(:birthday_year)
user.errors.messages.delete(:birthday_month)
user.errors.messages[:birthday_year] = ["生年月日は正しく入力してください"]
end
end
def delete_session
session.delete(:nickname)
session.delete(:email)
session.delete(:password)
session.delete(:password_confirmation)
session.delete(:lastname)
session.delete(:firstname)
session.delete(:lastname_kana)
session.delete(:firstname_kana)
session.delete(:birthday_year)
session.delete(:birthday_month)
session.delete(:birthday_day)
session.delete(:pid)
session.delete(:provider)
end
end
###リンクを通す
= button_to "メールアドレスで登録",step1_signups_path, method: :get, class: "login2-panel__login-form-inner--email"
# omniauth-rails_csrf_protectionを使うときはpostアクションを指定する
= button_to "facebookで登録", user_facebook_omniauth_authorize_path, method: :post
= button_to "googleで登録",user_google_oauth2_omniauth_authorize_path, method: :post
snsを使ってユーザ登録している場合はこれと同じURLでログインすることができる
###最後に
コードの可読性が悪いので皆さんのアドバイスを頂けると助かります!
少しでも勉強の手助けをできたらなと思います