#初めまして
今回外部APIを用いて初めて機能を作ったので備忘録として残したいと思います。
なお、APIの初期設定は割愛させていただきます。
Rails側だけのコード処理についてのみとなりますので、ご了承ください。
#開発環境
RubyonRails6
#導入について
今回、私はユーザー管理機能として「devise」gemを使用しております。
RailsでSNS認証を実装するためには「omniauth」というGemをします。
Google Facebookだけではなく、TwitterなどのSNSアカウントを用いてログイン機能などを実装できるみたいです。
また、「omniauth」以外にも「omniauth-rails_csrf_protection」というGemをインストールします。
これは、Omniauth認証はCSRF脆弱性が指摘されている為です。
Gemfile
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
gem "omniauth-rails_csrf_protection"
gem 'omniauth', '~>1.9.1'
bundle installをしましょう。
API登録する時は、環境変数を設定します。
自分は、MacOSがCatalinaなので
vim ~/.zshrc
↑のコマンド実行します。
iを押してインサートモードに移行
export FACEBOOK_CLIENT_ID='ご自身のアプリID'
export FACEBOOK_CLIENT_SECRET='ご自身のapp secret'
export GOOGLE_CLIENT_ID='ご自身のクライアントID'
export GOOGLE_CLIENT_SECRET='ご自身のクライアントシークレット'
編集が終わったらescapeキーを押してから:wqと入力して保存して終了
この時、気をつけないといけないのが、元々ある記述を消さないようにすることです。
間違えて消してしまってPCの動作がおかしくなったなんてこともあるみたいです!
ターミナルで
source ~/.zshrc
これで環境変数の設定が完了しました。
次は、アプリ側で環境変数を読み込みます。
config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']
end
ここでの記述で余分にスペースを開けたり、誤字に気をつけてください。
不備があると、あとで使用するときにうまく実装することができません。
→自分はここで記述をミスして、1時間ほどパニクリました
次に、SnsCredentialモデルを作成します。
何故作成するかというと「SNS認証とユーザー登録のタイミングが異なる」使用の為、SNS認証時にはusersテーブルのレコードを作成することはできないのです。
ですので、SNSに関わる別テーブルを用意した方がベストだということです!
マイグレーションファイル
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
uidとproviderをデータベースに保存するので、記述します。そして、アソシエーションのために、外部キーとしてuser_idを持たせます。
Userモデルを編集します。
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]
:omniauthable, omniauth_providers: [:facebook, :google_oauth2]の記述によりAPIを使用できるようになります。
アソシエーションを組みます。
user.rb
has_many :sns_credentials
sns_credential.rb
belongs_to :user
アソシエーション完了です。
deviseのクラスを継承したコントローラーを生成します。
このコントローラー内に標準で実装されているdeviseの設定を上書きすることによって、deviseが提供するアクションの内容を設定できます。
rails g devise:controllers users
route.rb
Rails.application.routes.draw do
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations'
}
end
ビューにAPIサインアップ、ログインを呼び出す記述をします。
registrations/new.html.erb
<%= link_to "Facebookで登録", user_facebook_omniauth_authorize_path, method: :post%>
<%= link_to "Googleで登録", user_google_oauth2_omniauth_authorize_path, method: :post%>
sessions/new.html.erb
<%= 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)
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
end
#SNS認証を実現させるためのメソッドをコントローラーに書きます。
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
authorization
end
def google_oauth2
authorization
end
private
def authorization
sns_info = User.from_omniauth(request.env["omniauth.auth"])
@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
authorizationはprivateメソッド内に記述し、authorizationを呼び出す「facebook」と「google_oauth2」というアクションを定義しています。
Userモデルから送られるデータをビューで扱えるようにします。
@userには「nickname」と「email」の情報をもたせて、SNS認証の判断は「sns_id」で行うため、idだけで扱えるようにします。
ビューの編集をします。
SNS認証を行っているかいないかで条件分岐をします。
パスワード入力を不要にするためです。
registrations/new.html.erb
#省略
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<%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 %>
#省略
<% end %>
コントローラーを編集します
users/registrations_controller.rb
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
#省略
superメソッドを使うと、親モデルの同名のメソッドを実行します。今回であれば「registrations#create」が実行されます。
params[:sns_auth]を取得した時だけ、「Devise.friendly_token」を使ってパスワードを自動生成し、あとの処理はsuperメソッドでdeviseのregistrations#createが実行します。
最後に
長々と書いてしまいましたが、上記の方法でローカル環境では実装することができました。
最後まで読んでいただき、ありがとうございました。