SNSログイン機能
SNSログイン機能の機能実装は共通している部分が多いため、一気にGoogle, Twitter, Instagramのログイン機能を実装する。
個別で必要な時は適宜不必要な部分を削除して使用してください。
環境
OS:macOS Monterey ver 12.0.1 (M1, 2020)
Ruby:3.2.1
Rails:7.0.5
MySQL:8.0.33
OAuth:2.0
技術
OAuth
OmniAuth
Gem
全対象:Devise(認証), omniauth-rails_csrf_protection(CSRF攻撃対策)
Google認証:omniauth-google-oauth2
Twitter認証:omniauth-twitter
Facebook認証:omniauth-facebook
前提条件
Deviceを用いた新規登録・ログイン・ログアウト機能が完成している
準備
GoogleAPI登録(Google認証に必要)
前提条件
Gmailを持っている
Google Cloud Platformにログイン
Google Cloud Platformで任意の名前でプロジェクト作成
OAuth同意画面でプロジェクト詳細設定
公開ステータス:テスト
ユーザーの種類:外部
テストユーザー:開発環境で動作確認する時に使用するメールアドレス(普段使ってるGmailでOK)
認証情報画面でOAuth2.0クライアントID,クライアントシークレット発行
名前:任意の名前
承認済みのJavaScript生成元:http://localhost, http://localhost:3000 (2つ記述する)
承認済みのリダイレクトURI:ログイン後にリダイレクトさせたいURLを記述
上記を記述し終えるとクライアントID, クライアントシークレットが発行されるのでメモする(漏洩注意)
TwitterAPI登録(Twitter認証に必要)
前提条件
メールアドレス・電話番号登録済みのアカウント
鍵アカでないこと
TwitterDevelopePortal でログイン
「Sign up for Free Account」から新規登録
無料・有料アカウントあるが、今回は無料アカウントでOK
「Describe all of your use case of Twitter’s data and API」欄でAPI使用目的を英語で250文字以上記述
そこまで重要なものではないので結構適当で大丈夫
プロジェクト新規作成
デフォルトで存在するプロジェクトでも可
[ プロジェクト名 ] → [ User authentication settings ]
App permissions:Request email from usersをON
Type of App:Web App, Automated App or Bot
App info:Callback URI / Redirect URL に
https://127.0.0.1:3000/users/auth/twitter/callback, http://127.0.0.1:3000/users/auth/twitter/callback を入力
🚨Twitterはlocalhostを使用できないので「127.0.0.1:3000」を使用する
WebsiteURL, Terms of service, Privacy policy:現在既に有効なWebsiteのURLを貼る
[ プロジェクト名 ] → [ Keys and tokens ]
API Key と API secretをメモ(漏洩注意)
🚨ClientID, ClientSecretは使用しないので注意
FacebookAPI登録(Facebook認証に必要)
前提条件
Facebookのアカウントを持っている
MetaforDeveloperアクセス→ [ スタート ]
Registar, Contact info, About youの3項目入力
新規アプリ作成
「ユーザーにFacebookアカウントでのログインを許可する」から作成
プロジェクト詳細設定
詳細設定後に発行されるアプリIDとapp secretをメモ(漏洩注意)
実装
Gemインストール
gem "omniauth-google-oauth2" ## Google認証
gem "omniauth-twitter" ##Twitter認証
gem "omniauth-facebook" ## Facebook認証
gem "omniauth-rails_csrf_protection" ## どの認証でも必要
Usersテーブルにカラム追加
db/migrate/00000000000000_devise_create_users.rbに以下のカラムを追加
t.string :provider
t.string :uid
SnsCredentialsテーブル作成
以下のように新しくSnsCredentialsテーブルを作成
class CreateSnsCredentials < ActiveRecord::Migration[7.0]
def change
create_table :sns_credentials do |t|
t.string :provider
t.string :uid
t.references :user, type: :string, foreign_key: true
t.timestamps
end
end
end
Userモデル, SnsCredentialモデルにリレーション関係記述
Userモデル
has_many :sns_credential, dependent: :destroy
SnsCredentialモデル
belongs_to :user
Userモデルのdeviseにomniauth系の記述追記
deviseでdeviseのヘルパーメソッドが使えるようになる
devise :database_authenticatable, :registerable,
:recoverable, :rememberable,
:omniauthable, omniauth_providers: %i[google_oauth2 twitter facebook]
credentialsにIDとシークレット系を保存
EDITOR=vi rails credentials:edit
ターミナル上で以下のように記述(SNS名, 変数名は適宜変更)
google:
client_id: ID
client_secret: シークレット
環境変数設定
config/initializer/devise.rb
config.omniauth :google_oauth2, Rails.application.credentials.google[:client_id], Rails.application.credentials.google[:client_secret], skip_jwt: true
config.omniauth :twitter, Rails.application.credentials.twitter[:client_id], Rails.application.credentials.twitter[:client_secret], skip_jwt: true
config.omniauth :facebook, Rails.application.credentials.facebook[:client_id], Rails.application.credentials.facebook[:client_secret], skip_jwt: true
Model編集
🚨Devise.friendly_token[12]の12の部分は自分のプロジェクトのパスワードの桁数のバリデーション等に応じて適宜変更
(ユーザーはSNS認証でパスワードを要求されないが、deviseの仕様としてパスワードの保存が必須なので代わりに裏側で自動的にパスワードを入れている)
user.rb
def self.from_omniauth(auth)
find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[12]
end
end
Controller作成
omniauth_callbacks_controller.rb作成
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
callback_for(:google)
end
def twitter
callback_for(:twitter)
end
def facebook
callback_for(:facebook)
end
def callback_for(provider)
@user = User.from_omniauth(request.env['omniauth.auth'])
@user.save!
if @user.persisted?
sign_in @user
redirect_to platforms_path
else
redirect_to new_user_registration_path
end
end
def failure
redirect_to root_path and return
end
end
Routing編集
devise_for :users, controllers: {
registrations: 'users/registrations',
sessions: 'users/sessions',
passwords: 'users/passwords',
omniauth_callbacks: "users/omniauth_callbacks"
}
Viewにボタン設置
## Google
<%= link_to user_google_oauth2_omniauth_authorize_path, method: :post do %>
Googleアカウントでサインインする
<% end %>
## Twitter
<%= link_to user_twitter_omniauth_authorize_path, method: :post do %>
Twitterアカウントでサインインする
<% end %>
## Facebook
<%= link_to user_facebook_omniauth_authorize_path, method: :post do %>
Facebookアカウントでサインインする
<% end %>
補足
パスワードについて
SNS認証はパスワードはユーザーに要求されないので、ユーザー全員にt対してパスワードのバリデーションをかけている場合は、SNS認証で登録・ログインする人には要求しないようにする
(以下はproviderが存在するかどうかで判別している)
class PasswordFormatValidator < ActiveModel::EachValidator
VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[A-Za-z\d]{8,12}\z/
def validate_each(record, attribute, value)
unless record.provider.present?
unless value =~ VALID_PASSWORD_REGEX
record.errors.add(attribute, (options[:message] || 'が正しい形式ではありませんんん'))
end
end
end
end
authの中身
下記のようにさまざまな情報を取得することができる。
下記はauthで取得できる情報の一部なのでデバッグで確認してみてほしい。
User.rbのself.from_omniauth(auth)のメソッド内で確認可能
{"provider"=>"twitter",
"uid"=>"0000000000000000000",
"info"=>
{"nickname"=>"000000",
"name"=>"田嶋太一",
"email"=>"00000000@gmail.com",
"location"=>"東京 世田谷",
"image"=>"画像のURL",
"description"=>"",
"urls"=>{"Website"=>0000000, "Twitter"=>"0000000"}},
"credentials"=>{"token"=>"0000000000000000000000", "secret"=>"Qx42N9jWKc5zjMnl8xDU3b1VJplRJEi4hqBei97vAoUzJ"},
確認
新規登録・ログインの確認
MySQLのテーブル内に登録したメールアドレス, uid, providerが保存されていて2回目以降もログイン可能で成功
🚨複数SNSで同一メアドが登録されないようにするためにメールアドレスにuniqueを適用した方が良い
まとめ
各SNSのID,シークレット取得
必要なGemインストール
Usersテーブルにカラム追加
SnsCredentialテーブル作成
User, SnsCredentialリレーション関連付け
deviseにomniauth追記
credentialsでID,シークレット管理
環境変数設定
Modelでauthのデータ取得
Controllerで各SNSの処理振り分け
Routingでomniauthのページを返せるようにする
Viewにボタン設置
(パスワードバリデーション関連)
(authデバッグ)
データベース保存確認,動作確認
🚨各バージョン、開発者ツールの画面仕様の変更等で違う場合あり