（ローカル環境）Rails における `devise + Omniauth` の Google 認証の導入メモ

# はじめに
* devise および OmniAuth に関連する資料は有志の方々によって作成されていますが、いざ自分が導入するとかなり時間を要したため自分のためにも記事にしました。尚、ご指摘箇所がございましたらご教授いただけますと幸いです。
* 随時更新する予定です

## 現状および前提条件など
### 現状
https://qiita.com/nakad119/items/967e53ace5788afdbcc1

* 上の記事の通り、「devise は導入した」まで作業を進行したことを前提としている
    * `devise`によって生成される devise モデルではなく、既に作成していた User モデルを利用した形

### 前提条件など
* 開発環境について以下に列挙
    * Windows11 Home 24H2
    * Ubuntsu
    * Docker
    * VScode
    * Rails7.2.1
    * devise4.9

* `Devise` では `users` テーブルでユーザー情報を管理しているため、**モデルは `User`** になっています
* 当記事は[公式の手順書](https://github.com/heartcombo/devise?tab=readme-ov-file)通りに実装していき一部変更を加えながら作業した結果実装できたのでベースは[公式の手順書](https://github.com/heartcombo/devise?tab=readme-ov-file)です
---
# 1. Google 認証利用に際しての認証情報の設定および取得（2024/10/22時点での操作）
Google 認証を使用する場合、"[omniauth-google-oauth2](https://github.com/zquestz/omniauth-google-oauth2?tab=readme-ov-file)" の手順にある通り『**"Google クライアント ID"** と **"Google クライアントシークレット"** という API キーを取得』する必要があるため、それら必要な情報を取得するのが目的

> Get your API key at: https://code.google.com/apis/console/ Note the Client ID and the Client Secret.

:::note info
ザックリ説明としては『認証サーバー(Google 側) に対して、どのクライアント(Google認証を利用したい Web アプリ側) が Google 認証を利用するか』を URI など含めて設定する
:::
　
<font color="red">＊以下の記事が完成されすぎており大変参考になります。よって当記事では割愛しております。ご了承ください。</font> → よろしければ[こちら](https://qiita.com/nakad119/items/a2780894558c95ff17a0)も併せてご一読ください

https://zenn.dev/tteaoocl/articles/5601b28fa8045d

# 2. Gem のインストール
* 以下のような Gem を追加しました
    * `omniauth`は不要だったかもしれません
```ruby
gem "devise", "~> 4.9" 　　　　　　　 # 追加
gem 'omniauth' 　　　　　　　　　　　　# 追加(もしかしたら不要？)
gem 'omniauth-rails_csrf_protection' # 追加
gem 'omniauth-google-oauth2' 　　　　 # 追加


group :development, :test do
    # ---(中略)---
    gem 'dotenv' # 追加
end
```

<details><summary>（メモ） gem 'omniauth' が不要とかもしれない理由</summary>
1. インストールしていなくても動作して方の記事が散見されたため
1. deviseの手順書（https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview）にも対象のプロバイダーの OmniAuth ジェムを導入するだけで omniauth を導入しているかは不明なため
</details>

# 3. アカウントを管理するテーブルにカラムを追加
* 以下のコマンドの通り、2 つのカラムを追加する
```ruby:User モデルへのカラム追加
rails g migration AddOmniauthToUsers provider:string uid:string
rake db:migrate
```

# 4. プロバイダーの指定
* 以下のように環境変数を受け取るような形でプロバイダーを宣言
```config/initializers/devise.rb
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
```

:::note info
他の方の記事で「カンマの後に空白を空けたらエラーになった」という旨の記述が見受けられましたが、私の場合は関係なかったです
:::

# 5. モデルに `omniauthable` を付与
* users がアカウント情報を管理するモデルなので User モデルに記述
* 他に `:registerable` なども記述されていればそれらに追記するだけ
```app/models/user.rb
devise :omniauthable, omniauth_providers: [:google_oauth2]
```

# 6. ルーティングを定義
* 後述するコントローラーのアクションへ誘導するためのルーティングを追記
    * `devise_for` のスコープ内に記述する点は注意
* サインアウトする際ののアクションへ誘導するためのルーティングを追記
    * コチラは `devise_for` のスコープには含めない
```ruby:config/routes.rb
devise_for :users, controllers: { 
    # ---(中略)---
    omniauth_callbacks: 'users/omniauth_callbacks' # 追記
}
# 以下も追記
devise_scope :user do
  delete 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end
```

# 7. コントローラーの生成と設定
* コントローラーを生成してアクションを記述
    * <font color="red">私の場合は既に同じ名称のコントローラーが生成されていたためそれを利用しました</font>（無い方は実行して生成）
```:名前空間を users にしてコマンド実行
bin/rails g controller users::omniauth_callbacks 
```

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

  def google_oauth2
    provider = 'google'
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication
      set_flash_message(:notice, :success, kind: "#{ provider }") if is_navigational_format?
    else
      session["devise.#{ provider }_data"] = request.env["omniauth.auth"].except(:extra)
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end
```
# 8. 認証を行ったユーザー情報の取得処理をモデルに追記
* User モデルに認証を行ったユーザーの情報を取得して保存する処理を追記する
```ruby:app/models/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[0, 20]
    user.name = auth.info.name
    user.image = auth.info.image
  end
end
```
* この場合、画像情報も受け取って保存をしようとしているため画像保存のロジックが別に必要

:::note info
* 私の場合はユーザー名、画像が不要だったため該当箇所を削除しました
```ruby:app/models/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[0, 20]
    end
  end
```
:::

# 9. ビュー側にログイン・ログアウトのリンクを設置する
:::note warn
必ずリクエストメソッドは `POST` とする
:::

```ruby:Google 認証のログインリンクの一例
<%= button_to "グーグルでのログイン", user_google_oauth2_omniauth_authorize_path, method: :post, data: { turbo: false } %>
```
* `button_to` によって `form` 要素が挿入されて明示的に記述しなくてもリクエストメソッドが `POST` になる
* <font color="red">`link_to` で実装可能ですが私は上手く機能しなくて不採用にしました</font>

　
```ruby:Google 認証のログアウトリンクの一例
<%= link_to "ログアウト", destroy_user_session_path %>
```



---
### 個人的に躓いたポイント
1. Devise の手順書も Facebook での認証を導入する事例を挙げていたため Google の場合だとどうなるか分からなかった
    * 基本は facebook の箇所を google-oauth2 に置き換えるだけで良いという具合で安心した
1. ジェムの `dotenv` を入れていなかったため環境変数を格納していた `.env` を読み込めていなかった
    * ローカルで Google 認証に必要となる API キーを格納するには必須だった
    * 本番環境では不要なファイルなので `.gitignore` の対称である
    * `dotenv` の理解が無かったため「credentials の編集」を行ったがこれも変化なしだった
        * おそらくこちらが本番環境には必須と思われる

---
### 不明点
* この場合 `omniauth` の gem 必要だったのか
* なぜか `link_to` で実装可能ですが私の場合は上手く機能しませんでした
    * 以下のように POST メソッドを指定して、Turbo もオフにしたのですがダメでした

```ruby:link_to の場合
<%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, method: :post, data: { turbo: false } %>
```

---
### 参考資料
※リンクカードの方が分かりやすいのでリンクカードにしております。非常に間延びしておりますがご了承ください。

https://cloud.google.com/application-integration/docs/configure-authentication-profiles?hl=ja#auth-types

https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview

https://github.com/omniauth/omniauth

https://zenn.dev/tteaoocl/articles/5601b28fa8045d

https://www.nips.ac.jp/~murata/o2popper/doc/get-client-secret-ja.pdf

https://zenn.dev/tteaoocl/articles/0f0e6443cd318f

https://zenn.dev/funxxfun/articles/59f56a85aa7a4f

https://zenn.dev/yoiyoicho/articles/c44a80e4bb4515

https://zenn.dev/fusic/articles/2b80861427484d

https://zenn.dev/redheadchloe/articles/81dec8a2ba5e4a

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

https://gc.cs.okayama-u.ac.jp/lab/nom/articles/ikeda-y-20160713-100824

https://qiita.com/yoshi_4/items/580374b30242a265ec22

https://zenn.dev/redheadchloe/articles/879f143ee54602#%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E7%94%A8%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%83%BC%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B

https://github.com/omniauth/omniauth/wiki/List-of-Strategies

https://qiita.com/iloveomelette/items/b6e65caea88b6e86cfdd

https://qiita.com/at-946/items/8630ddd411d1e6a651c6

https://youtu.be/CnZnwV38cjo?si=hRTcLt_uKRcrj48U

https://qiita.com/wa-chan222/items/0a237d0b10be255bac2f