🦓

[Rails]deviseとomniauthによるgoogleログイン

2023/08/05に公開

はじめに

deviseominiauthを使ってgoogleログイン機能を作っていきます。
deviseのユーザー認証機能が完成した状態から作っていきます。

https://github.com/zquestz/omniauth-google-oauth2

環境

Rails 7.0.4.3
ruby 3.2.1

googleプロジェクトを作成する

console.cloud.google.com にアクセスし、新規プロジェクトを作成します。

OAuth同意画面

左のメニューから、「APIとサービス」>「OAuth同意画面」へクリックします。
User Typeを「外部」にし、「作成」をクリックします。
次の画面でアプリに関する情報を入れます。

スコープ

スコープにユーザーのメールとプロフィールを追加します。
何もしなくても大丈そうです。

テストユーザー

テスト用のメールアドレスを入れます。

認証情報

「クライアントID」をクリックします。
ウェブアプリケーションを選択し、名前を入れます。
URL:http://localhost:3000
リダイレクトURL:http://localhost:3000/users/auth/google_auth2/callback

次の画面ではクライアントIDとクライアントシークレットが作成されます。

コマンドを実行してクレデンシャルファイルに保存します。

EDITOR="code --wait" rails credentials:edit -e development
var/folders/xxx/xxx/development.yml
google:
  google_client_id: ***
  google_client_secret: ***

gemをインストールする

こちらのgemが必要です。

Gemfile
gem "devise", "~> 4.9"
gem 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-github'
gem 'omniauth-google-oauth2'
bundle install

クレデンシャルを追加する

274行目あたりに追加します。

app/config/initializers/devise.rb
Devise.setup.do |config|
...
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
config.omniauth :google_oauth2, 
                 Rails.application.credentials.google[:google_client_id],
                 Rails.application.credentials.google[:google_client_secret]
end

Oauth用カラムを追加する

UserテーブルにOauth用のuidproviderカラムを追加します。

bin/rails g migration AddOauthToUser provider:string uid:string  
      invoke  active_record
      create    db/migrate/20230804142243_add_oauth_to_user.rb
bin/rails db:migrate
== 20230804142243 AddOauthToUser: migrating ===================================
-- add_column(:users, :provider, :string)
   -> 0.0163s
-- add_column(:users, :uid, :string)
   -> 0.0011s
== 20230804142243 AddOauthToUser: migrated (0.0176s) ==========================

Userモデルを設定する

Oauth関連の設定を追加します。

app/models/user.rb
class User < ApplicationRecord

  devise :omniauthable, omniauth_providers: %i[google_oauth2]
  
  validates :uid, uniqueness: { scope: :provider }
end

コールバック用コントローラーを定義する

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { 
    omniauth_callbacks: 'users/omniauth_callbacks'
  }
end

コールバック用コントローラーを作成する

deviseのuserコントローラーにあるomniauth_callbacksが必要です。
なかったら作成します。

bin/rails g controller users::omniauth_callbacks 
      create  app/controllers/users/omniauth_callbacks_controller.rb

コールバックコントローラーにgoogle_oauth2アクションを作成する

google loginで取得したauthを変数に代入し、Userに渡します。
p authauthオブジェクトを確認することができます。

app/controllers/users/omniauth_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  skip_before_action :verify_authenticity_token, only: :google_oauth2

  def google_oauth2
          @user = User.from_omniauth(request.env["omniauth.auth"])
      
          if @user.persisted?
            sign_in_and_redirect @user, event: :authentication
            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, alert: @user.errors.full_messages.join("\n")
          end
  end
  
  def failure
    redirect_to root_path, alert: "Authentication failed, please try again."
  end
  
  private

  def auth
    auth = request.env['omniauth.auth']
  end
end

ユーザーモデルにてself.from_omniauthアクションを作成する

Oauthでログインされる際に、authオブジェクトによるユーザーの認証もしくは作成についてアクションを定義します。

app/models/user.rb
class User < ApplicationRecord
...
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.info.email
      user.name = auth.info.name
      user.password = Devise.friendly_token[0,20]
      user.avatar = auth.info.image
      user.skip_confirmation!
    end
  end

end

ログでログインの流れを確認します。

00:45:54 web.1  | Started POST "/users/auth/google_oauth2" for ::1 at 2023-08-05 00:45:54 +0900
00:45:54 web.1  | D, [2023-08-05T00:45:54.074473 #16078] DEBUG -- omniauth: (google_oauth2) Request phase initiated.
00:45:54 web.1  | Started GET "/users/auth/google_oauth2/callback?state=******" for ::1 at 2023-08-05 00:45:54 +0900
00:45:54 web.1  | D, [2023-08-05T00:45:54.468639 #16078] DEBUG -- omniauth: (google_oauth2) Callback phase initiated.
00:45:54 web.1  | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
00:45:54 web.1  | Processing by Users::OmniauthCallbacksController#google_oauth2 as HTML
00:45:54 web.1  |   Parameters: {"state"=>"***", "code"=>"***", "scope"=>"email profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid", "authuser"=>"0", "prompt"=>"none"}
00:45:54 web.1  | #<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=true expires_at=1691167553 scope="https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid" token="************"> extra=#<OmniAuth::AuthHash id_info=#<OmniAuth::AuthHash at_hash="IXifCXI6wjYOxlm_ZkSO2Q" aud="*******" azp="*******" email="***.***@gmail.com" email_verified=true exp=1691167554 family_name="***" given_name="***" iat=1691163954 iss="https://accounts.google.com" locale="ja" name="*** ***" picture="https://lh3.googleusercontent.com/a/AAcHTte5Xrr7D-h2l-9VTGNcCEsn2c8WyrvrioD5PuZzIJ2S=s96-c" sub="106074554495937262913"> id_token="*********" raw_info=#<SnakyHash::StringKeyed email="***.***@gmail.com" email_verified=true family_name="***" given_name="***" locale="ja" name="*** ***" picture="https://lh3.googleusercontent.com/a/AAcHTte5Xrr7D-h2l-9VTGNcCEsn2c8WyrvrioD5PuZzIJ2S=s96-c" sub="106074554495937262913">> info=#<OmniAuth::AuthHash::InfoHash email="***.***@gmail.com" email_verified=true first_name="***" image="https://lh3.googleusercontent.com/a/AAcHTte5Xrr7D-h2l-9VTGNcCEsn2c8WyrvrioD5PuZzIJ2S=s96-c" last_name="***" name="*** ***" unverified_email="***.***@gmail.com"> provider="google_oauth2" uid="106074554495937262913">
00:45:54 web.1  |   User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."provider" = $1 AND "users"."uid" = $2 ORDER BY "users"."id" ASC LIMIT $3  [["provider", "google_oauth2"], ["uid", "106074554495937262913"], ["LIMIT", 1]]
00:45:54 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   TRANSACTION (0.1ms)  BEGIN
00:45:55 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "***.***@gmail.com"], ["LIMIT", 1]]
00:45:55 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   User Exists? (0.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."uid" = $1 AND "users"."provider" = $2 LIMIT $3  [["uid", "106074554495937262913"], ["provider", "google_oauth2"], ["LIMIT", 1]]
00:45:55 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   User Create (0.3ms)  INSERT INTO "users" ("email", "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at", "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", "confirmation_token", "confirmed_at", "confirmation_sent_at", "unconfirmed_email", "failed_attempts", "unlock_token", "locked_at", "created_at", "updated_at", "name", "avatar", "description", "provider", "uid") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) RETURNING "id"  [["email", "***.***@gmail.com"], ["encrypted_password", "[FILTERED]"], ["reset_password_token", "[FILTERED]"], ["reset_password_sent_at", "[FILTERED]"], ["remember_created_at", nil], ["sign_in_count", 0], ["current_sign_in_at", nil], ["last_sign_in_at", nil], ["current_sign_in_ip", nil], ["last_sign_in_ip", nil], ["confirmation_token", "[FILTERED]"], ["confirmed_at", "2023-08-05 00:45:55.199276"], ["confirmation_sent_at", nil], ["unconfirmed_email", nil], ["failed_attempts", 0], ["unlock_token", "[FILTERED]"], ["locked_at", nil], ["created_at", "2023-08-05 00:45:55.202578"], ["updated_at", "2023-08-05 00:45:55.202578"], ["name", "***"], ["avatar", "https://lh3.googleusercontent.com/a/AAcHTte5Xrr7D-h2l-9VTGNcCEsn2c8WyrvrioD5PuZzIJ2S=s96-c"], ["description", nil], ["provider", "google_oauth2"], ["uid", "106074554495937262913"]]
00:45:55 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   TRANSACTION (0.3ms)  COMMIT
00:45:55 web.1  |   ↳ app/models/user.rb:20:in `from_omniauth'
00:45:55 web.1  |   TRANSACTION (0.1ms)  BEGIN
00:45:55 web.1  |   ↳ app/controllers/users/omniauth_callbacks_controller.rb:16:in `google_oauth2'
00:45:55 web.1  |   User Update (0.7ms)  UPDATE "users" SET "sign_in_count" = $1, "current_sign_in_at" = $2, "last_sign_in_at" = $3, "current_sign_in_ip" = $4, "last_sign_in_ip" = $5, "updated_at" = $6 WHERE "users"."id" = $7  [["sign_in_count", 1], ["current_sign_in_at", "2023-08-05 00:45:55.205885"], ["last_sign_in_at", "2023-08-05 00:45:55.205885"], ["current_sign_in_ip", "::1"], ["last_sign_in_ip", "::1"], ["updated_at", "2023-08-05 00:45:55.205978"], ["id", 5]]
00:45:55 web.1  |   ↳ app/controllers/users/omniauth_callbacks_controller.rb:16:in `google_oauth2'
00:45:55 web.1  |   TRANSACTION (0.3ms)  COMMIT
00:45:55 web.1  |   ↳ app/controllers/users/omniauth_callbacks_controller.rb:16:in `google_oauth2'
00:45:55 web.1  | Redirected to http://localhost:3000/ideas
00:45:55 web.1  | Completed 302 Found in 213ms (ActiveRecord: 2.5ms | Allocations: 10529)
00:45:55 web.1  | 

ビューを作成する

ログインされたユーザーの名前、画像などをビューに表示させます。
一般のユーザーログイン後の処理と同じのでここでは省略します。

Googleログインボタン

公式サイトからログインボタンをダウンロードし、アプリに入れます。
https://developers.google.com/identity/branding-guidelines?hl=ja

おわりに

deviseomniauthによるgoogleログイン機能でした。
過去にgithubログイン機能も実装しましたので良かったら見てみてください。
https://zenn.dev/redheadchloe/articles/879f143ee54602

Discussion