[Rails]deviseとomniauthによるgoogleログイン
はじめに
devise
とominiauth
を使ってgoogleログイン機能を作っていきます。
deviseのユーザー認証機能が完成した状態から作っていきます。
環境
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
google:
google_client_id: ***
google_client_secret: ***
gemをインストールする
こちらのgemが必要です。
gem "devise", "~> 4.9"
gem 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-github'
gem 'omniauth-google-oauth2'
bundle install
クレデンシャルを追加する
274行目あたりに追加します。
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用のuid
とprovider
カラムを追加します。
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関連の設定を追加します。
class User < ApplicationRecord
devise :omniauthable, omniauth_providers: %i[google_oauth2]
validates :uid, uniqueness: { scope: :provider }
end
コールバック用コントローラーを定義する
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 auth
でauth
オブジェクトを確認することができます。
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
オブジェクトによるユーザーの認証もしくは作成についてアクションを定義します。
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ログインボタン
公式サイトからログインボタンをダウンロードし、アプリに入れます。
おわりに
devise
とomniauth
によるgoogle
ログイン機能でした。
過去にgithub
ログイン機能も実装しましたので良かったら見てみてください。
Discussion