[Rails]SNS認証(Twitter、Facebook、Google)機能の実装
#はじめに
今回はRailsアプリにおけるdeviseによるSNS認証での、新規登録・ログイン機能の実装方法を解説します。
#前提条件
・deviseによるユーザー管理機能を実装済み
・SNS認証の外部APIを登録済み
外部APIの登録手順は以下の記事がわかりやすいです。
#機能の仕様
・ Twitter/Facebook/Google登録を押すとSNS認証が始まり、ニックネームとメールアドレスが入力された状態でユーザー登録が始まる
・SNS認証での新規登録の際はパスワードが自動生成され、新規登録できる
#手順
1)APIの設定
こちらは最初にも書いたように上記記事を参考に行ってください。
2)RailsアプリにSNS認証を実装
2-1)Gemのインストール
gem 'omniauth-twitteer'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
# omniauth認証はCSRF脆弱性が指摘されているため対策としてインストール
gem 'omniauth-rails_csrf_protection'
# 環境変数を管理するためインストール(vim ~/.zshrcで定義することも可能)
gem 'dotenv-rails'
Gemrile
に記述したら忘れずbundle install
しましょう。
dotenv-rails
については以下を参考にしてみてください。
% bundle install
2-2)環境変数の設定
% vim ~/.zshrc
# iを押してインサートモードにして入力
export TWITTER_API_KEY = 'メモしたID'
export TWITTER_API_SECRET_KEY = 'メモしたSECRET'
export FACEBOOK_API_KEY = 'メモしたID'
export FACEBOOK_API_SECRET_KEY = 'メモしたSECRET'
export GOOGLE_API_KEY='メモしたID'
export GOOGLE_API_SECRET_KEY='メモしたSECRET'
# 定義したらesc→:wqで保存
保存したら下記コマンドを実行し設定を反映させましょう。
% source ~/.zshrc
gem dotenv-rails
インストールしている場合は.env
ファイルをアプリディレクトリに作成し、そのファイル内に記述していきます。
TWITTER_API_KEY = 'メモしたID'
TWITTER_API_SECRET_KEY = 'メモしたSECRET'
FACEBOOK_API_KEY = 'メモしたID'
FACEBOOK_API_SECRET_KEY = 'メモしたSECRET'
GOOGLE_API_KEY = 'メモしたID'
GOOGLE_API_SECRET_KEY = 'メモしたSECRET'
記述が完了したら、pushしないようにgitignore
ファイルに.env
を追加します。
/.env
###2-3)アプリ側で環境変数を読み込む
config/initializers/devise.rb
ファイルを編集します。
Devise.setup do |config|
# 省略
config.omniauth :twitter,ENV['TWITTER_API_KEY'],ENV['TWITTER_API_SECRET_KEY']
config.omniauth :facebook,ENV['FACEBOOK_API_KEY'],ENV['FACEBOOK_API_SECRET_KEY']
config.omniauth :google_oauth2,ENV['GOOGLE_API_KEY'],ENV['GOOGLE_API_SECRET_KEY']
end
環境変数の設定は以上です。
#3)SNS認証機能のサーバーサイド実装
###3-1)SNS認証用のモデルの作成
SNS認証時はAPIにリクエストを送って、認証を行います。
そのためusersテーブルとは別にSNS認証用のテーブルを作成する必要があります。
% rails g model sns_credential
class CreateSnsCredentials < ActiveRecord::Migration[6.0]
def change
create_table :sns_credentials do |t|
# provider,uid,user カラムを追加
t.string :provider
t.string :uid
t.references :user, foreign_key: true
t.timestamps
end
end
end
編集できたらrails db:migrate
を実行します。
###3-2)UserモデルとSnsCredentialモデルの編集
deviseでOmniAuthを使えるよう編集していきます。
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:twitter, :facebook, :google_oauth2]
has_many :sns_credentials
class SnsCredential < ApplicationRecord
belongs_to :user
end
###3-3)deviseのコントローラーの設定
ターミナルで下記コマンドを実行し、deviseのコントローラーを作成します。
% rails g devise:controlers users
コントローラーを作成したら、deviseのルーティングを設定します。
Rails.application.routes.draw do
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations'
}
root to: 'users#index'
end
ここまででSNS認証を実現するための準備が完了です。
もう少し頑張りましょう。
#4)SNS認証を行うためのメソッドの実装
###4-1)メソッドの実装
上記のドキュメントにもありますが、deviseのコントローラー内にメソッドを定義していきます。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def twitter
authorization
end
def facebook
authorization
end
def google_oauth2
authorization
end
private
def authorization
@user = User.from_omniauth(request.env["omniauth.auth"])
end
end
次に定義したアクションをビューで呼び出します。
<%= link_to 'Twitterで登録', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post%>
次にUserモデル
にメソッドを作成します。
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, :google_oauth2]
has_many :sns_credentials
# クラスメソッドを定義する
def self.from_omniauth(auth)
# 定義できたら「binding.pry」を記述しSNSから情報を取得できるか確認してみましょう
end
end
認証ボタンで登録すると処理が止まりますので、ターミナルでauth
と入力し情報を取得できているか確認してみましょう。
確認できたら、メソッドの中身を記述していきます。
def self.from_omniauth(auth)
sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
end
処理についてはfirst_or_createメソッド
を使うことで、DBに保存するかどうかを判断しています。
次にSNS認証を行っていなかった(新規登録の場合)にDBに検索をかけるように記述を加えます。
def self.from_omniauth(auth)
sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
# sns認証したことがあればアソシエーションで取得
# 無ければemailでユーザー検索して取得orビルド(保存はしない)
user = User.where(email: auth.info.email).first_or_initialize(
nickname: auth.info.name,
email: auth.info.email
)
end
first_or_initializeメソッド
を用いて検索をかけることでDBに新規レコードを保存しないように処理を行えます。
4-2)Userモデルからの処理を記述
MVCの流れに沿って、モデルの処理をコントローラーで記述していきます。
# 省略
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
# 省略
ここまでで新規登録機能の実装が完了しました。
#5)ログイン機能の実装
###5-1)Userモデルの編集
ログイン時の処理を記述していきます。
def self.from_omniauth(auth)
sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
# sns認証したことがあればアソシエーションで取得
# 無ければemailでユーザー検索して取得orビルド(保存はしない)
user = User.where(email: auth.info.email).first_or_initialize(
nickname: auth.info.name,
email: auth.info.email
)
# 以下を追記
# userが登録済みであるか判断
if user.persisted?
sns.user = user
sns.save
end
{ user: user, sns: sns }
end
次にビューを編集します。
OmniAuthは新規登録とログインを兼ねているためパスは同じです。
<%= link_to 'Twitterでログイン', user_twitter_omniauth_authorize_path, method: :post%>
<%= link_to 'Facebookでログイン', user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to 'Googleでログイン', user_google_oauth2_omniauth_authorize_path, method: :post%>
以上でログイン機能の実装は終了です。
最後にSNS認証時のパスワード入力をしなくてもいいように実装していきます。
###5-2)パスワード入力についての処理実装
sns_credentialモデルにoptional: true
というオプションを追加します。このオプションをつけることで外部キーの値がなくても保存できるようになります。
class SnsCredential < ApplicationRecord
belongs_to :user, optional: true
end
コントローラーに以下の記述を追加
Class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
#中略
def authorization
sns_info = User.from_omniauth(request.env["omniauth.auth"])
# @user と @sns_id を追加
@user = sns_info[:user]
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
else
@sns_id = sns_info[:sns].id
render template: 'devise/registrations/new'
end
end
end
ビューファイルのpasswordのフォームで、SNS認証を行っているかの条件分岐を記述します。
<%if @sns_id.present? %>
<%= hidden_field_tag :sns_auth, true %>
<% else %>
<div class="field">
<%= f.label :password %>
<% @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<% end %>
最後にdeviseのcreateアクション
を作動させるように、コメントアウトを外し下記のようなを記述します。
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
def create
if params[:sns_auth] == 'true'
pass = Devise.friendly_token
params[:user][:password] = pass
params[:user][:password_confirmation] = pass
end
super
end
#省略
このように記述することで、ビューファイルからのparamsから送信されてきた値を保存することができます。
これで完全にSNS認証機能の実装は終了です。
自分のアプリで使うのは初めてだったので備忘録として記録してこうと思い作成しました。
お役に立てば幸いです。
#参考文献
OmniAuthのGitHub
Discussion