【Rails】Sorceryを使用したGoogle認証機能の追加

# はじめに
こんにちは。未経験からエンジニア転職を目指しプログラミングスクールRUNTEQにて学習しているokadaと申します。
さて、早速ですが、ソーシャルログイン機能をご存知でしょうか？これは、ユーザーがGoogleやLINEなどの既存のソーシャルメディアアカウントを使用し、ウェブサイトやアプリに簡単にログインできる機能のことです。この機能を導入することで、ユーザーの会員登録やログインの手間が大幅に削減でき、非常に便利です。
今回は、Sorceryを用いたRailsアプリケーションでのGoogle認証の追加方法について記載していきたいと思います。アプリ開発中の方や、これから開発を考えている方の参考になれば嬉しいです。

# 環境
- Rails 7.0.5
- Ruby 3.2.2
- Sorcery 0.16.5

前提条件として、SorceryのSimple Password Authentication（メールアドレス、パスワードを用いた認証）はすでに実装された状態であるものとします。

# 1. Google APIの設定
まずは[Google APIのコンソール](https://console.cloud.google.com/)にて設定を行っていきます。
この手順については以下の記事でとても分かりやすく説明されておりますので割愛させていただきます。
[【Rails7】Googleログインを公式ドキュメントに沿って実装する](https://zenn.dev/yoiyoicho/articles/c44a80e4bb4515#google-api%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86)

# 2. credentials.ymlにclient ID, client secretを追加
APIの設定が完了したら、生成されたclient ID, client secretをcredentials.ymlに追加します。
ターミナルで下記のコマンドを実行し、credentials.ymlの編集画面を開きます。
```
EDITOR="vim" bin/rails credentials:edit
```
インサートモードにし、client ID, client secretを記載します。
```
google:
  google_client_id: 取得したclient_id
  google_client_secret: 取得したclient_secret
```
※ :（コロン）の後ろには半角スペースが必要です。半角スペースがないとエラーが発生する可能性があります。
編集が終了したら上書き保存をします。

# 3. Sorcery externalモジュールの導入
次に、Sorceryのサブモジュールをインストールします。
ターミナルで下記のコマンドを実行します。
```
rails g sorcery:install external --only-submodules
```
# 4. authenticationsテーブル、Authenticationモデルの作成
externalモジュールをインストールするとauthenticationsテーブルのmigrationファイルが自動生成されます。
migrationを実行し、authenticationsテーブルを作成します。
```
rails db:migrate
```
さらに、Authenticationモデルを作成します。
migrationファイルは必要ないため、migration=falseオプションをつけコマンドを実行します。
```
rails g model Authentication --migration=false
```
Authenticationモデルが作成できたらUserモデルとAuthenticationモデルの関連づけを行います。
それぞれのモデルに以下のようにAssociationを追加します。
```ruby:app/models/user.rb
class User < ActiveRecord::Base
 has_many :authentications, :dependent => :destroy
 accepts_nested_attributes_for :authentications
end
```
```ruby:app/models/authentication.rb
class Authentication < ActiveRecord::Base
 belongs_to :user
end
```
# 5. sorcery.rbファイルの設定
sorcery.rbに設定を追加していきます。
```ruby:config/initializers/sorcery.rb

# 70行目あたり  

#externalモジュールの読み込み
Rails.application.config.sorcery.submodules = [:external]

Rails.application.config.sorcery.configure do |config|

  #利用する外部サービスのプロバイダーを指定
  config.external_providers = %i[google]


# 160行目あたり

  #credentials.ymlから情報を取得
  config.google.key = Rails.application.credentials.dig(:google, :google_client_id)
  config.google.secret = Rails.application.credentials.dig(:google, :google_client_secret)
  #API設定で承認済みのリダイレクトURIとして登録したurlを設定
  config.google.callback_url = 'http://localhost:3000/oauth/callback?provider=google'
  #外部サービスから取得したユーザー情報をUserモデルの指定した属性にマッピング
  config.google.user_info_mapping = {:email => "email", :username => "name"}
end

  config.user_config do |user|
    #外部サービスとの認証情報を保存するモデルを指定
    user.authentications_class = Authentication


```


# 6. OauthsControllerの作成
認証の処理を扱うためのControllerを作成します。
下記のようにoauthとcallbackアクションを追加します。
```
rails g controller Oauths oauth callback
```
oauthアクションとcallbackアクションを記述していきます。
ここでは一例を挙げさせていただきます。
```ruby:oauths_controller.rb
class OauthsController < ApplicationController
skip_before_action :require_login
  def oauth
    #指定されたプロバイダの認証ページにユーザーをリダイレクトさせる
    login_at(auth_params[:provider])
  end

  def callback
    provider = auth_params[:provider]
    # 既存のユーザーをプロバイダ情報を元に検索し、存在すればログイン
    if (@user = login_from(provider))
      redirect_to root_path, notice:"#{provider.titleize}アカウントでログインしました"
    else
      begin
        # ユーザーが存在しない場合はプロバイダ情報を元に新規ユーザーを作成し、ログイン
        signup_and_login(provider)
        redirect_to root_path, notice:"#{provider.titleize}アカウントでログインしました"
      rescue
        redirect_to root_path, alert:"#{provider.titleize}アカウントでのログインに失敗しました"
      end
    end
  end

  private

  def auth_params
    params.permit(:code, :provider)
  end

  def signup_and_login(provider)
    @user = create_from(provider)
    reset_session
    auto_login(@user)
  end
end
```
ここで使用されている`login_at(provider)`,`login_from(provider)`,`create_from(provider)`,`auto_login(@user)`はSorceryであらかじめ用意されているメソッドです。
https://github.com/Sorcery/sorcery#external

# 7. routing設定
oauthアクションとcallbackアクションのroutingを設定します
```ruby:config/routes.rb
post "oauth/callback" => "oauths#callback"
get "oauth/callback" => "oauths#callback" 
get "oauth/:provider" => "oauths#oauth", :as => :auth_at_provider
```
# 8. リンクの設置
最後にリンクを設置します。
引数としてsorceryの`auth_at_provider_path(provider)`メソッドを使用することで外部モジュールのURLに遷移させることができます。
```
<%= link_to 'Login with Google', auth_at_provider_path(provider: :google) %>
```
リンクをクリックし、無事ログインできれば成功です。



### 補足：本番環境のcallback_url適用
先ほどはsorcery.rbに直接callback_urlを記述しましたが、実際は開発環境と本番環境でそれぞれ異なるurlにリダイレクトさせる必要があります。
以下にgem 'config'を使用した方法を記載します。
- `Gemfile`に`gem 'config'`を追加し、`bundle install`を実行してインストール
- `config/settings/`ディレクトリ下に`development.yml`と`production.yml`を作成
- それぞれのファイルに開発環境、本番環境のcallback_urlを記載
```config/settings/development.yml
sorcery:
    google_callback_url: 'http://localhost:3000/oauth/callback?provider=google'
```
```config/settings/production.yml
sorcery:
    google_callback_url: 'https://your_app_url/oauth/callback?provider=google'
```
- sorcery.rbの記述を変更
```ruby:
config.google.callback_url = Settings.sorcery[:google_callback_url]
```

# おわりに
いかがでしたでしょうか。この記事が少しでも役立てたら嬉しいです。
また、実務未経験のため間違っている部分や不足があるかもしれません。もし気づいた点等ございましたら、コメントにてご指摘いただけますと幸いです。

# 参考にしたサイト・記事
[Sorcery公式Wiki](https://github.com/Sorcery/sorcery/wiki/External)
[GoogleAPI公式サイト](https://developers.google.com/identity/gsi/web/guides/overview?hl=ja)
https://zenn.dev/yoiyoicho/articles/c44a80e4bb4515
https://qiita.com/tarakish/items/8e61106fa2473a9d97e8
https://blog.aiandrox.com/posts/tech/2020/03/29/