概要
今回は標題の通り、Devise + Omniauth でGoogle認証の実装手順を記していきます。
なぜ今さら記すことに至ったかですが、参照させていただいた記事を軸に進めていたのですが、所々エラーになってしまったり、プラスで追加しなければいけないものもあるので、今後導入する方の参考になればと思ったからです。
他のSNS認証でも大枠は同じだと思うので、Twitterなど導入したいものがあれば置き換えて参考にしてくだされば幸いです。
尚、ご指摘箇所がございましたら
ご教授いただけますと幸いです。
はじめに
では、早速といきたいところですが先に私の環境と前提をお話ししていきます。
- macOS Monterey 12.3.1 (M1)
- Rails 6.1.5
- MariaDB 10.6.7
そして、今回私はDevise
を導入しており、後ほどuid
カラムとprovider
カラムを追加するのですがUser
モデルではなく別のSns_Credential
モデルに追加する手順で進めていきます。
また、Devise
の各種実装は既に完了していることを前提としております。
それでは、お待たせしました。手順を記していきます。
IDとシークレットを取得
先にGoogle Cloud Platformにてこちらの記事を参考にしながら、クライアントID
とクライアントシークレット
を取得しておきましょう。
5分程度で終了するので、先に済ませてしまいます。
IDとシークレットは後ほどファイルに記述していくため、必ずどこかしらにメモしておいてください!
そして絶対に他人に教えたりしないでください!
Gemのインストール
IDとシークレットを取得できたら、次はomniauth系のgemなどをインストールしていきます。
今回、私はIDとシークレットをcrendentials.yml.enc
に記載していきます。
もし、.env
に記述する方は記述方法や手順が若干異なりますので注意してください。
gem "omniauth-google-oauth2"
gem "omniauth-rails_csrf_protection"
# '.env'を使う場合は以下も追加
gem "dotenv-rails"
omniauth-rails_csrf_protection
をなぜ追加したかというと、OmniAuthの許可されたリクエストメソッドをGETで使用するとCSRF攻撃を受けてしまう可能性があるため、GETを無効にしてくれるGemです。DeviseのGitHubでも追加してねと言われています。
CSRF攻撃とは...
クロスサイトリクエストフォージェリのアルファベット頭文字を取った略称で、Webアプリケーションの脆弱性を利用した攻撃手法のこと。
攻撃用Webページにユーザを誘導し、不正なリクエストを攻撃対象のWebアプリケーションに送信します。攻撃対象のWebアプリケーションはその不正なリクエスト処理してユーザが意図していない処理が行われてしまいます。
ユーザに直接的な被害はないものの、攻撃対象のWebアプリケーションからは攻撃者と見なされてしまいます。
他のSNS認証に関してもDeviseのGitHubによると以下のように言っています。詳しいリストも公式にはあるので、ぜひ参考にしてください。
Generally, the gem name is
omniauth-#{provider}
where provider can be "facebook" or "twitter", for example.
基本的にジェムの名前は「omniauth-#{provider}
」で、例えば"provider"のところに"facebook"や"twitter"が入るよ。
Gemfile
に追加ができたらbundle install
を行いましょう。
$ bundle install
# '.env'を使う方は以下も実施(アプリのディレクトリで)
$ touch .env
.env
の方は、必ず.gitignore
に追加をしてください!
ここに入れないとGitHubから.env
は丸見えとなり悪意あるユーザが悪用してしまう可能性があります。
crendentials.yml.enc
の場合は以下のコマンドをターミナルで実行してVimを開きます。
$ EDITOR=vi rails credentials:edit
Vimが開けたら以下を追加します。
*Vimの使い方は他の記事を参照してください。
*yaml形式なのでインデント幅には注意してください。半角スペース1つ間違えるだけでエラーとなります。
google:
client_id: アプリID
client_secret: アプリシークレット
.env
の場合は以下の通りです。
GOOGLE_CLIENT_ID='******'
GOOGLE_CLIENT_SECRET='******'
アプリIDとアプリシークレットの読み込み
アプリIDとアプリシークレットを入力できたら、次はこれらを読み込んでいきます。
config/initializers/devise.rb
に以下を追加していきます。
Devise.setup do |config|
# 省略
# ===== credentials.yml.encに記載した場合 =====
config.omniauth :google_oauth2, Rails.application.credentials.google[:client_id], Rails.application.credentials.google[:client_secret], skip_jwt: true
# ===== .envに記載した場合 =====
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], skip_jwt: true
OmniAuth.config.logger = Rails.logger if Rails.env.development? # debug用
# 省略
end
Google認証する場合skip_jwt: true
をつけないとJWT::InvalidIatError
が発生するみたいなので、忘れずに記入します。credentials.yml.enc
と.env
で書き方が異なるので注意してください。
では、追加ができたらターミナルでちゃんと設定されているか確認を行います。
$ rails c
# 起動できたら以下を実行('credentials.yml.enc'の場合)
> Rails.application.credentials.google[:client_id]
# '.env'の場合
> ENV['GOOGLE_CLIENT_ID']
Sns_Crendentialモデルの作成
では、Sns_Crendentialモデルを作成します。
$ rails g model sns_credential
class CreateSnsCredentials < ActiveRecord::Migration[5.2]
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
$ rails db:migrate
各モデルの編集
では、次にアソシエーションやメソッドなど必要なものを設定していきます。
コメントで解説を行っていきます。記述が長い場合は後に記述します。
class SnsCredential < ApplicationRecord
# ===== 以下を追加 =====
belongs_to :user
end
class User < ApplicationRecord
# ===== 以下を追加 =====
has_many :sns_credential, dependent: :destroy
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
# ===== Omniauthを使用するためのオプションを追加 =====
:omniauthable, omniauth_providers: %i[google_oauth2]
class << self # ここからクラスメソッドで、メソッドの最初につける'self.'を省略できる
# SnsCredentialsテーブルにデータがないときの処理
def without_sns_data(auth)
user = User.where(email: auth.info.email).first
if user.present?
sns = SnsCredential.create(
uid: auth.uid,
provider: auth.provider,
user_id: user.id
)
else # User.newの記事があるが、newは保存までは行わないのでcreateで保存をかける
user = User.create(
name: auth.info.name, # デフォルトから追加したカラムがあれば記入
email: auth.info.email,
profile_image: auth.info.image, # デフォルトから追加したカラム
password: Devise.friendly_token(10) # 10文字の予測不能な文字列を生成する
)
sns = SnsCredential.create(
user_id: user.id,
uid: auth.uid,
provider: auth.provider
)
end
{ user:, sns: } # ハッシュ形式で呼び出し元に返す
end
# SnsCredentialsテーブルにデータがあるときの処理
def with_sns_data(auth, snscredential)
user = User.where(id: snscredential.user_id).first
# 変数userの中身が空文字, 空白文字, false, nilの時の処理
if user.blank?
user = User.create(
name: auth.info.name,
email: auth.info.email,
profile_image: auth.info.image,
password: Devise.friendly_token(10)
)
end
{ user: }
end
# Googleアカウントの情報をそれぞれの変数に格納して上記のメソッドに振り分ける処理
def find_oauth(auth)
uid = auth.uid
provider = auth.provider
snscredential = SnsCredential.where(uid:, provider:).first
if snscredential.present?
user = with_sns_data(auth, snscredential)[:user]
sns = snscredential
else
user = without_sns_data(auth)[:user]
sns = without_sns_data(auth)[:sns]
end
{ user:, sns: }
end
end
end
User.create
の引数はUserモデルでNOTNULL
制約しているカラムは入れるようにしないとバリデーションエラーが発生します。
auth
は後に記述する@omniauth
すなわちrequest.env['omniauth.auth']
です。
request.env['omniauth.auth']
にはたくさんの情報が詰め込まれています。
一部を抜粋すると以下のようなものです。
=> {"provider"=>"google_oauth2",
"uid"=>"****",
"info"=>
{"name"=>"****",
"email"=>"****",
"unverified_email"=>"****",
"email_verified"=>true,
"first_name"=>"****",
"last_name"=>"****",
"image"=>"****"},
...
このようにハッシュ形式で渡ってきます。ぜひbinding.pry
などで確認してみてください。
このパラメータを上記の変数などに格納しています。
OmniauthCallbacksControllerの作成と編集
では、コントローラを作成するためにターミナルにて以下を実行しましょう。
# 以下のフォルダがあれば実行しない
$ mkdir app/controllers/users/
# 以下は実行する
$ touch app/controllers/users/omniauth_callbacks_controller.rb
作成できたらコントローラ内を記述していきます。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
callback_for(:google)
end
def callback_for(provider)
@omniauth = request.env["omniauth.auth"]
info = User.find_oauth(@omniauth)
@user = info[:user]
if @user.persisted? # persisted?は保存が完了しているかを評価するメソッド
sign_in_and_redirect @user, event: :authentication
# is_navigational_formatはフラッシュメッセージを発行する必要があるかどうかを確認する
# capitalizeは文字列の先頭を大文字に、それ以外は小文字に変更して返すメソッド
set_flash_message(:notice, :success, kind: provider.to_s.capitalize) if is_navigational_format?
else
@sns = info[:sns]
render template: "devise/registrations/new"
end
end
def failure
redirect_to root_path and return
end
end
ルーティングの設定
続いて、ルーティングを以下のように設定します。
「ここを追加」以外は私の場合ですので、違っていても構いません。
Rails.application.routes.draw do
root "homes#index"
devise_for :users, controllers: {
registrations: "users/registrations",
passwords: "users/passwords",
# ===== 以下を追加 =====
omniauth_callbacks: "users/omniauth_callbacks"
}
# 省略
end
追加すると新しいルーティングが設定されます。具体的には以下の通りです。
見にくいですが、ご容赦ください。
$ rails routes | grep user
user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users/omniauth_callbacks#passthru
user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2
Viewの編集
それでは、ビュー側にリンクを追加していきましょう。
私の場合はFont AwesomeでGoogleのアイコンを追加しています。
link_to
のブロック内は好みでカスタマイズしてください。
<div>
<%= link_to user_google_oauth2_omniauth_authorize_path, method: :post do %>
<i class="fa-brands fa-google"></i>
Googleアカウントでサインインする
<% end %>
</div>
link_to
にはmethod: :post
をつけてあげないとCSRF攻撃関連で上手く動作しない可能性があるので、しっかりつけてあげてください。
こちらの記事を引用すると
link_toを使うとデフォルトでGETメソッドになるため、もしログインのリンクにmethod: :postを入れていなければ、omniauth-rails_csrf_protectionのgemがGETを無効にしているのでMissing templateのエラーになってしまいます。
尚、ログインページにもapp/views/devise/shared/_links.html.erb
を参照して表示されているので、そこも併せて編集するのも良いでしょう。
本番環境の設定はこちらの記事を参照してください。
お疲れ様でした。
最後までお読みいただきありがとうございます。
参考
- devise - GitHub
- OmniAuth: Overview
- RailsでFacebookとGoogleのOAuth連携。SNS認証の方法
- 【Rails】DeviseとOmniauthを使ってGoogle、Twitter、Facebook認証
- Rails+omniauth-google-oauth2でGoogleログイン(devise無し)
- 【Rails】deviseを用いたSNS認証(Facebook・Google)の導入方法を簡単に解説
- [Rails] Facebook/Twitter/Googleでのユーザー登録をDevise & Omniauthを使って爆速で実装する
- RailsにGoogleのSNS認証を分かりやすく導入する方法~omniauth-google-oauth2~