Help us understand the problem. What is going on with this article?

devise + omniauth で google/facebook/twitterアカウントでログイン認証を実装した際のメモ

はじめに

devise と omniauth を利用したSNSアカウントでのログイン認証を実装してみました。
プログラム自体、書き始めてまだ2か月程なので、間違ってる部分とかかなりある気がしますが、
あくまでも自分用メモとして残しておこうと思います!

もしも参考にしていただける場合は、この記事を過信しすぎないようお願いします…(´・ω・`)

また間違いなどがありましたら、コメントいただけますと幸いですm(_ _)m

動作環境

  • AWS Cloud9
  • ruby on rails -v 5.2.2
  • devise
  • omniauth-facebook (6.0.0)
  • omniauth-google-oauth2 (0.8.0)
  • omniauth-twitter (1.4.0)

Gemfileにgemを追加して保存

Gemfile
gem 'devise'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'

gemのインストール

terminal
$ bundle install

Deviseのインストール

terminal
$ rails g devise:install

DeviseでUserモデルの作成

terminal
$ rails g devise User

作成されたモデル

app/model/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable  
end

gem omniauth を利用するために、devise:内に:omniauthable を追加する。

app/model/user.rb(omniauthable追加した状態)
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable
end

今回は、通常アカウント作成時にはユーザー名の登録を必須としたいのと、
SNSアカウントを利用したログインの場合はSNSアカウントで利用しているユーザー名をそのまま利用したいので、
usersテーブルにnameカラムを追加します。
また、Facebook/Twitter/Googleアカウントでログインする場合に必要になる下記3つのカラムも同時に追加します。

追加するカラム
name/provide/uid/image

terminal
$ rails g migration add_columns_to_users provider uid name image

name については名無しは不可としたいので、マイグレーションファイルを開いて、
nameカラムにnull: falseを指定し、マイグレートします。

db/migrate/add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :name, :string, null: false
    add_column :users, :provider, :string
    add_column :users, :uid, :string
    add_column :users, :image, :string
  end
end
terminal
$ rails db:migrate

Twitter/Google/Facebook Developerアカウントの取得

こちらについては、まとめるとえらい時間かかりそうなので省略します・・・。
今後時間があるときにまとめようと思いますので、その時に追記します!
現時点でわからない方は、ググって頑張りましょう!
(完全初心者の僕でもどうにかなったので、たぶん大丈夫!)

OAuthの設定

omniauthを利用するための初期設定

devise.rbの250行目付近にある omniauth の設定箇所にて、
facebook/twitter/googleのプロバイダーを設定します。

環境変数の設定および、各種プロバイダーのApp_ID/Keyの取得については、
省略しますので、頑張ってググってください!

config/initialize/devise.rb
 # ==> OmniAuth
  # Add a new OmniAuth provider. Check the wiki for more information on setting
  # up on your models and hooks.
  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
  config.omniauth :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET'] #追加
  config.omniauth :twitter, ENV['TW_APP_ID'], ENV['TW_APP_SECRET'] #追加
  config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'] #追加

今回はgem 'dotenv'で環境変数を管理するので、Railsアプリのルートディレクトリに.envファイルを作成し、
中身に以下の通り記述します。

.env
FB_APP_ID= Facebook Developersで取得したアプリIDを記載
FB_APP_SECRET= Facebook Developersで取得したシークレットキーを記載
TW_APP_ID= Twitter Developersで取得したアプリIDを記載
TW_APP_SECRET= Twitter Developersで取得したシークレットキーを記載
GOOGLE_CLIENT_ID= Google APIsで取得したクライアントIDを記載
GOOGLE_CLIENT_SECRET= Google APIsで取得したシークレットキーを記載

Userモデルにメソッドを追加

app/models/user.rbに以下のメソッドを追加します。

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable 

  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.provider = auth.provider
      user.uid = auth.uid
      user.name = auth.info.name
      user.email = User.dummy_email(auth)
      user.password = Devise.friendly_token[0, 20]
      user.image = auth.info.image.gsub("_normal","") if user.provider == "twitter"
      user.image = auth.info.image.gsub("picture","picture?type=large") if user.provider == "facebook"
      user.image = auth.info.image if user.provider == "google_oauth2"
    end 
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end
end

where(provider: auth.provider, uid: auth.uid).first_or_create

各種SNSアカウントでログインを試みたユーザーのporovider/uidが存在するかを確認。

  1. 存在していた場合:見つかったユーザーでそのままログインする
  2. 存在していなかった場合:アカウントが自動生成され、そのままログインする ***

user.provider = auth.provider

ログインに使用したプロバイダー名をusersテーブルのproviderカラムに追加
プロバイダー名は、twitter/facebook/googleの内、
config/initialize/devise.rbに追加したconfig.omniauthで設定したプロバイダ名が入る

user.uid = auth.uid

ログインユーザー識別用IDをusersテーブルのuidカラムに追加

user.name = auth.info.name

ログインに使用したアカウントのユーザー名を取得し、usersテーブルのnameカラムに追加
例)Twitterアカウントのユーザー名がまひろだとすると、そのまままひろがnameカラムに追加される

user.email = User.dummy_email(auth)

バリデーションでメールアドレスの登録は必須としているので、
providerとuidをミックスしてダミーメールアドレスを作成している

ダミーメールアドレスを作成しているself.dummy_email(auth)メソッドはprivate部分に記述してます。

※各種デベロッパー側の設定によって、SNSアカウントのメールアドレスを取得できるようだけど、
 設定がややこしくてよくわからなかったので、とりあえず今回はダミーメールアドレス作成で乗り切ります

app/models/user.rb
  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end

user.password = Devise.friendly_token[0, 20]

ログイン時にパスワード入力などは行わないが、パスワードは必須の為、
バリデーションに引っかかってエラーにならないようランダムパスワードを生成しています

user.image = auth.info.image.gsub("_normal","") if user.provider == "twitter"

user.providertwitterの時、auth.info.image.gsub("_normal","")が実行されます。
auth.info.imageでTwitterのプロフィール画像のURLを取得しています。
※imageカラムに追加されるのは、画像ではなく、取得したプロフィール画像のURLとなります。

.gsub("_normal","")は、取得したプロフィール画像のURLから"_normal"という文字列を""(空文字)に置き換えています。
これを行う事で、取得したプロフィール画像が荒くなるのを防いでいます。
(※わかりやすくいうと、"_normal"を空文字に置き換える事で、実質的には指定の文字列を削除しています)

user.image = auth.info.image.gsub("picture","picture?type=large") if user.provider == "facebook"

user.providerfacebookの時、auth.info.image.gsub("picture","picture?type=large")が実行されます。
auth.info.imageでFacebookのプロフィール画像のURLを取得しています。
※imageカラムに追加されるのは、画像ではなく、取得したプロフィール画像のURLとなります。

.gsub("picture","picture?type=large")は、取得したプロフィール画像のURLから
"picture"という文字列を"picture?type=large"に置き換えています。
これを行う事で、大きめのプロフィール画像が取得されます。
(後でプロフィール画像のサイズを調整する際に荒くなりにくい)

user.image = auth.info.image if user.provider == "google_oauth2"

user.providergoogle_oauth2の時、auth.info.imageが実行されます。
auth.info.imageでGoogleのプロフィール画像のURLを取得しています。
※imageカラムに追加されるのは、画像ではなく、取得したプロフィール画像のURLとなります。

画像サイズについては、デフォルトでちょうどよいサイズなので特に指定はしていません。
サイズを変えたい場合は、取得したURLの末尾に=wサイズの値(例:=w500)を追加する処理を書く事で、
プロフィール画像のサイズを任意に変更できます。

omniauth_callback_controllerの作成

omniauthを使ってログインする際のコールバックコントローラを作成します。
初期状態で、devise/omniauth_callbacks#Actionが設定されていますが、
controllerフォルダにdeviseというフォルダがなく、実体化されていない?ため、必須の作業となるようです。
(※軽くググった限りでは、deviseフォルダのomniauth_callbacksを編集する方法については出てこなかったです…)

terminal
$ rails g devise:controllers users
app/controllers/users/
omniauth_callbacks_controller.rb # 使うのはこれだけ
confirmations_controller.rb
passwords_controller.rb
registrations_controller.rb
sessions_controller.rb
unlocks_controller.rb

コントローラを変更したいのは、omniauth_callbacks_controller.rbのみで、
他はdeviseの標準のコントローラを利用するため、他のコントローラファイルは削除します。

routes.rbにルーティングを設定します。

routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: "toppages#index"

  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
end

生成されたomniauth_callbacks_controller.rbを開いて、以下の内容を記述します。

omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def twitter
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "Twitter") if is_navigational_format?
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def google_oauth2
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "Google") if is_navigational_format?
    else
      session["devise.google_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end

end

メソッド名は必ず、config.omniauthで設定したプロバイダー名を利用しましょう。
勝手なメソッド名をつけるとエラーになります。

これで、URLからドメイン名/users/sign_upにアクセスして、
各種プロバイダーでのサインアップリンクをクリックすれば、SNSアカウントでのログインに成功すると思います。

以上で完了です。
細かいデザインなどはまた別の機会にやる事にします。

終わり

書くのめっちゃ疲れたけど、だいぶ頭の中が整理できた気がする…

間違ってる部分もあると思います…(間違っている箇所に気づける程、まだ慣れていません…
もし気づいた点などありましたら、コメントください・・・

LuckHackMahiro
現在、転職活動もしながら、コードを書く毎日を過ごしています🧐 目指すはフルスタックエンジニア!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした