こんにちは、ふーがです。
フィヨルドブートキャンプでプログラミングを学習しています。
現在は自作サービスを作成しているところで、そのサービスでGoogleログインを導入したので、その手順を残しておきます。
- 環境
- 前提
- GoogleのクライアントIDとクライアントシークレットの取得
- Gemのインストール
- omniauth用のカラムを作成する
- Rails側の設定をする
- providerとuidにindexとunique制約を付ける
- 通常登録時にuidを埋める処理
- Google経由で登録したユーザーの更新
- まとめ
環境
- Ruby 3.0.2
- Ruby on Rails 6.1.4
- omniauth-google-oauth2 1.0.0
- oauth2 1.4.7
- omniauth 2.0.4
- omniauth-rails_csrf_protection 1.0.0
前提
devise
gemを使用してユーザー認証機能を実装している前提です。
deviseでの
ユーザー認証はたくさん記事があるので検索してみてください。
GoogleのクライアントIDとクライアントシークレットの取得
以下の記事がわかりやすく、手順も変わっていなさそうなので参照してください。
1点だけ注意点があり、手順9/10で入力するリダイレクトURIだけ変える必要があります。
具体的には、devise
を使う場合、リダイレクトURLの欄は
http://localhost:3000/users/auth/google_oauth2/callback
とする必要があります。
取得したクライアントIDとクライアントシークレットは.env
ファイルに記述しておくと良いでしょう。
Gemのインストール
# Gemfile gem 'omniauth-google-oauth2' gem 'omniauth-rails_csrf_protection'
omniauth-rails_csrf_protection
は、RailsでOmniauth gemを使用する際の、クロスサイトリクエストフォージェリによる攻撃を緩和するためのgemです。
現在(2021年9月)はこれが必須のようですが、omniauth-google-oauth2
をbundleしても自動では入らないので手動でbundleする必要があります。
また、認証リンクへのリクエストメソッドはPOST
である必要があります。
button_to
やlink_to ..., method: :post
を使わないと動かないので注意してください。
omniauth用のカラムを作成する
migrationを用意して、Userテーブルにカラムを追加します。
$ bin/rails g controller AddOmniauthToUsers proviser:string uid:string $ bin/rails g db:migrate
Rails側の設定をする
まずはconfig/initializers/devise.rb
に以下の設定を追記します。
# config/initializers/devise.rb config.omniauth :google_oauth2, ENV['GOOGLE_ID'], ENV['GOOGLE_SECRET'], {}
ルーティングも追記します。
#config/routes.rb devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
deviseを導入した際にdevise_for :users
は追加されているはずなので、今回はcontrollers: ...
の記述を追加していることになります。
これはcallback時に使用するコントローラーを明示的に指定しています。
ここで指定したコントローラーを実装します。
今後、他にもサービスプロバイダが増える可能性があるので、少し抽象度を上げた実装にしています。
# app/controllers/users/omniauth_callbacks_controller.rb class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController skip_before_action :verify_authenticity_token, only: :google_oauth2 def google_oauth2 callback_for(:google) end def callback_for(provider) @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.to_s.capitalize) 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
コントローラーに記述があるとおり、User#from_omniauth
を実装する必要があるので、Modelの方に実装していきます。
# app/models/user.rb def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.email = auth.info.email user.password = Devise.friendly_token[0, 20] end end
以上で設定完了です。
deviseはデフォルトでGoogle認証がマッピングされた時にはログインページにGoogleログイン用のリンクを出してくれるようになっています。
ここまで設定が終わればそのリンクが表示されるようになっているはずです。
providerとuidにindexとunique制約を付ける
同じサービスプロバイダから何度も登録できないように、providerとuidの組み合わせが一位になるようにunique制約を付けます。
$ bin/rails g migration AddIndexUidAndProviderToUsers
生成されたマイグレーションファイルを以下のように修正して、マイグレートを実行します。
class AddIndexUidAndProviderToUsers < ActiveRecord::Migration[6.1] def change add_index :users, %i[uid provider], unique: true end end
あわせて、Model側にもバリデーションを設定します。
# app/models/user.rb validates :uid, presence: true, uniqueness: { scope: :provider }, if: -> { uid.present? }
通常登録時にuidを埋める処理
前項で実行したマイグレートにより、omniauthを使用しない通常の登録時に問題が発生します。
通常の登録だと、uidもproviderも空文字で登録されるため、2人目以降のユーザー登録時にunique制約に引っかかってしまうわけです。
この問題に対処するため、通常のユーザー登録時にuidを埋める処理を差し込みます。
そのために、Devise::RegistrationsController
を継承したUsers::RegistrationsContorller
を作成します。
# app/controllers/users/registrations_controller.rb class Users::RegistrationsController < Devise::RegistrationsController def build_resource(hash = {}) hash[:uid] = User.create_unique_string super end end
Devise::RegistrationsController
で定義されているbuild_resource
メソッドをオーバーライドして、uidにランダムな文字列を詰め込んだ後、superを呼んで元の処理に返しています。
create_unique_string
メソッドは下記のようにしています。
# app/models/user.rb def self.create_unique_string SecureRandom.uuid end
メソッドをオーバーライドしたので、コントローラーの向き先も変更します。
# config/routes.rb devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks', registrations: 'users/registrations' }
Google経由で登録したユーザーの更新
deviseで実装したユーザー情報の更新は、デフォルトだと変更するときにパスワードの入力が必要です。
しかし、omniauthで登録したユーザーパスワードを自分で設定しないため、パスワードを入力できません。
このままだとメールアドレスの変更もできない状態になってしまうので、この問題に対処します。
パスワードの更新時以外はパスワードの入力を必須でなくします。
先ほど作成したapp/controllers/users/registrations_controller.rb
に以下を追記します。
# app/controllers/users/registrations_controller.rb def update_resource(resource, params) return super if params['password'].present? resource.update_without_password(params.except('current_password')) end
これもDevise::RegistrationsController
で定義されているメソッドをオーバーライドしている形です。
passwordフィールドが入力されている場合はsuperを呼んでそれ以外(パスワード更新時以外)は、パスワードなしで更新できるようにしています。
まとめ
omniauth-rails_csrf_protection
を入れなければいけないことと、認証リンクのHTTPメソッドはPOST
にしなくてはいけないという部分で若干ハマりました。
もしGoogleログインを実装する機会があれば、お気をつけください。