###はじめに
複数のSNSと連携可能な認証をつくります。
Deviseは実装済みとして進めます。
各APIキーの取得等にも触れませんので適宜お調べ下さい。
今回はfacebookとtwitterで実装しますが他のSNSでも基本的には同じだと思います。
###環境
Windows10
ruby 2.6.6
Rails 6.0.3.1
###下準備
#####gem追加
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
保存後bundle installして下さい。
#####credentials.ymlにAPIキーを記述
Windowsの方は前回の記事を参考にしてみてください。
facebook:
api_key: pk_test_~
secret_key: sk_test_~
twitter:
api_key: pk_test_~
secret_key: sk_test_~
#####Deviseの設定
config/initializer内。
#以下を追加
config.omniauth :facebook, Rails.application.credentials.facebook[:api_key], Rails.application.credentials.facebook[:secret_key], scope: 'email', info_fields: 'email,name'
config.omniauth :twitter, Rails.application.credentials.twitter[:api_key], Rails.application.credentials.twitter[:secret_key], scope: 'email', callback_url: 'https://localhost:3000/users/auth/twitter/callback'
twitterの方はコールバックURLを明示する必要があるみたいです。
#####Socialモデルを作成
今回はDeviseでUserモデルを作成済みとします。
Userモデルへカラムを追加してもいいですが、今回はUserモデルに紐づくSocialモデルを新たに作成します。
(UserモデルとSocialモデルは一対多の関係となります。)
rails g model social
Socialモデルにはuser_id、provider("facebook"や"twitter"などが入る)、uid(各SNSアカウントと紐づけるためのidが入る)を持たせます。
class CreateSocials < ActiveRecord::Migration[6.0]
def change
create_table :socials do |t|
t.references :user, null: false, foreign_key: true
t.string :provider, null: false
t.string :uid, null: false
t.timestamps
end
end
end
保存したらrails db:migrateする。
#####関連付け
has_many :socials, dependent: :destroy#追加
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable#追加
belongs_to :user
validates :uid, uniqueness: {scope: :provider}#同一provider内で多重登録できないようにする
同じSNSアカウントが複数のユーザーに紐付かないようにします。
「{scope: :provider}」は多分なくても平気だと思いますが念のため。
###実装
#####コールバック処理をつくる
devise_for :users, controllers: {
sessions: 'users/sessions',
password: 'users/password',
registrations: 'users/registrations',
omniauth_callbacks: 'users/omniauth_callbacks'#追加
}
以下app/controllers/users/omniauth_callbacks_controller.rb内。
(rails routes等で確認し、この中のメソッドを各リダイレクト先に設定して下さい。)
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
if request.env['omniauth.auth'].info.email.blank?#Facebookでメール使用を許可しているか
redirect_to '/users/auth/facebook?auth_type=rerequest&scope=email'
end
callback_from :facebook
end
def twitter
callback_from :twitter
end
private
def callback_from(provider)
provider = provider.to_s
if user_signed_in?
@user = current_user
User.attach_social(request.env['omniauth.auth'], @user.id)#後でattach_social作る。SNSからの情報とログイン中のUserのidを渡す。
else
@user = User.find_omniauth(request.env['omniauth.auth'])#後でfind_omniauth作る。SNSからの情報を渡す。
end
if @user.persisted?#登録済みor登録できたら
flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
sign_in_and_redirect @user, event: :authentication
else
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
end
リダイレクト元のSNSの種類を「callback_from」へ渡して切り分けます。
「callback_from」内では「SNS認証での新規ユーザー登録またはSNS認証済みユーザーのログイン」か「ログイン中ユーザーのSNS連携」かを切り分けました。
「登録後」「ログイン後」「連携後」の処理は今回は分けずに、全てユーザーページにリダイレクトさせています。
次にUser.rb内に各処理を書きます。
def self.find_omniauth(auth)#SNS認証での新規登録またはsnsログイン
social = Social.where(uid: auth.uid, provider: auth.provider).first
unless social.blank?#sns認証済み(ログイン)
user = User.find(social.user.id)
else#sns認証での新規登録
temp_pass = Devise.friendly_token[0,20]#今回は取り敢えずランダムなパスワードを作ります
user = User.create!(
username: auth.info.name,
email: auth.info.email,
password: temp_pass,
password_confirmation: temp_pass,
)
social = Social.create!(
user_id: user.id,
provider: auth.provider,
uid: auth.uid,
)
end
return user
end
def self.attach_social(auth, user_id)#sns連携追加時
social = Social.create!(
user_id: user_id,
provider: auth.provider,
uid: auth.uid,
)
end
「SNS認証での新規ユーザー登録またはSNS認証済みユーザーのログイン」時はさらに「新規登録」か「ログイン」かを切り分けます。
「新規登録」時はUserとSocialを両方作成し(同時にUserとSocialの紐づけもする)、Userを返します。
「ログイン」時はSNSから送られてきた情報(auth.uidとauth.provider)からSocialを探し、紐づくUserを返します。
「ログイン中ユーザーのSNS連携」時はSocialのみ作成します。ログイン中のUserと紐づけるため、user_idを受け取ってます。
一応これで処理は終わりです。あとは「user_facebook_omniauth_authorize_path」などでリンクを作成すれば、Userの状況に応じて新規登録やログイン、連携を行ってくれます。
###おしまい
実際には連携したSNSへのリンクを作ったり、SNSでの新規登録時のパスワード再設定の仕組み、Deviseのメール認証、連携の解除ボタンなどもあったほうが良いかと思いますが、ひとまず最小構成で作ってみました。
Rails勉強中なのでスマートでない所が多々あるかと思います。もしおかしなことをしてたら教えて頂けると嬉しいです!