2019年3月11日月曜日

Devise に omniauth-google-oauth2 でGoogle認証を追加する (Rails 5.2.2)

Rails 5系の情報がまだあまり無いようだったので。とりあえずうまくいった手順です。

結論

こちらを参考にGoogle Developers Console側の登録をした後、
爆速ッ!! gem omniauth-google-oauth2 で認証させる - Qiita

Railsアプリ側の設定は、omniauth-google-oauth2のREADMEに「Devise」の項があるので、それに従ってやればうまくいきました。
zquestz/omniauth-google-oauth2: Oauth2 strategy for Google > Devise

前提条件とやりたいこと

今回は以下を目指します。

  • 既にDeviseでUserというモデルを作成済みの所にGoogle認証機能を追加する
  • 既にそのメールアドレスで登録があればログイン、無ければGoogleからメールアドレスと名前を引っ張ってきて自動で新規登録する

背景として、とある会社のGSuiteアカウントでしかログインできない社内向けツールを作るために調べた情報です。
おそらくGSuiteに限らずログイン可なアプリを作る場合でもそんなに変わらないはず。


Google Developers Console側の登録

プロジェクトの作成

Google側の画面もちょっとずつ変わっているようなので現時点でのスクリーンショットを載せておきます。

https://console.developers.google.com/
にアクセス

特にAPIやサービスは有効化しなくて大丈夫です。

▼をクリックした先から新しいプロジェクトを作成します。
(確かまだ一つもプロジェクトが無い時は、もっとわかりやすい場所に「プロジェクトを作成」ボタンが出ていたような。)

プロジェクト名を付けます。一意なIDみたいなもので、ユーザーに見える箇所は特になかったはず。

私の場合は社内向けアプリにしたかったので組織・場所にはその会社のドメインを指定しました。

「OAuth同意画面」の設定

作成したアプリを選択して、画面左のメニューから「認証情報」を選択。
まずは「OAuth同意画面」の設定から行います。

  • アプリケーションの種類(私はログインを社内アカウントに限定したかったので「内部」を選択。どのアカウントでもログインできるようにするにはここで「公開」を選択します。)
  • アプリケーション名
  • サポートメールアドレス
を設定。

  • スコープ
  • 承認済みドメイン
を設定。

どのような情報を設定するか

アプリケーション名とサポートメールアドレスは、認証画面でユーザー向けに表示されます。信用できるアプリかどうか判断してもらうための情報になると思います。

こんな感じで表示されます。

スコープは、Googleアカウントからどんな情報を取得できるかの権限の設定。デフォルトはemail, profile, openidです。
(あんまりこの権限を広げすぎると、公開アプリの場合は審査が入るようです。)

承認済みドメインは、本番環境のアプリのドメインを入力します。(localhostはここで入力しなくても許可できます)
まだ分からなければ空欄で保存できます。

「保存」すると認証情報画面に戻ります。

クライアントID・シークレットの作成

次はクライアントID・シークレットを作成します。

認証情報タブに戻って「認証情報を作成」>「OAuthクライアントID」を選択。

作成画面でまずアプリケーションの種類に「ウェブ アプリケーション」を選択。

名前は、何に使っているID/シークレットか分かるような名前を付けておくと良さそうです。
(おそらくlocalhost用と本番環境用は分けた方が良さそう。共用すると、クライアントシークレットが漏れた時にどこのlocalhostからでも本番環境にアクセスできるようになってしまうのではないかと…?)

そして承認済みのリダイレクトURIに「http://localhost:3000/users/auth/google_oauth2/callback」を設定
(アプリの実装によって変わる場合もあります。後ほどrails/info/routesで確認できる)
(本番環境用のクライアントIDには本番環境のURIを設定。
※ここで、前のステップ「OAuth同意画面」の設定で「承認済みドメイン」に設定済みのドメインのURIしか設定できない。)

「作成」ボタンを押すとクライアントID、クライアントシークレットが発行されるので控えておく(後から確認することも可能)

これでGoogle Developer Console側の設定は完了です。

Railsアプリ側の設定

次にRailsアプリ側の設定を行っていきます。

Gemfile

まずはGemfileに’omniauth’と’omniauth-google-oauth2’を追加

# Gemfile

# Google login
gem 'omniauth'
gem 'omniauth-google-oauth2'

で、

bundle install

config/initializers/devise.rb

すでにDeviseでUserというモデルが作成されている前提で進めます。

config/initializers/devise.rb に先ほど取得したクライアントID、クライアントシークレットを設定
(dotenv-rails gemを使用して環境変数に保存しています。)

# config/initializers/devise.rb

  config.omniauth :google_oauth2,
                  ENV['GOOGLE_CLIENT_ID'],
                  ENV['GOOGLE_CLIENT_SECRET'],
                  {}

(なんとなく元々のコメントでomniauthの話が書いてあるところの近くに書きました。)

.env ファイルに追加(Herokuなら環境変数に追加)

# .env

GOOGLE_CLIENT_ID=myclientid
GOOGLE_CLIENT_SECRET=myclientsecret

「myclientid」「myclientsecret」の部分を、Google Developer Consoleで作成したクライアントID、クライアントシークレットに置き換えてください。

config/routes.rb

元々

# config/routes.rb
  devise_for :users, controllers: {
    sessions: 'users/sessions'
  }

となっていたところを、下記の通り変更

# config/routes.rb
  devise_for :users, controllers: {
      omniauth_callbacks: 'users/omniauth_callbacks'
  }

app/models/user.rb

User modelのdeviseの設定に :omniauthable を追加
併せて omniauth_providers: [:google_oauth2] を追加

元々こんな感じだったのを

# app/models/user.rb
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

追加。

# app/models/user.rb
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable,
         omniauth_providers: [:google_oauth2]

app/controllers/users/omniauth_callbacks_controller.rb

コメントだけでほぼからっぽなファイルがDeviseに自動生成されていると思うので、

# app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  # You should configure your model like this:
  # devise :omniauthable, omniauth_providers: [:twitter]

##### 中略 #####

end

そこに google_oauth2 メソッドを追加。(omniauth-google-oauth2のREADMEに書いてあるのをそのままコピペ)

# app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def google_oauth2
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted?
      flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'Google'
      sign_in_and_redirect @user, event: :authentication
    else
      session['devise.google_data'] = request.env['omniauth.auth'].except(:extra) # Removing extra as it can overflow some session stores
      redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
    end
  end

end

(ここで .except(:extra) が無いと ActionDispatch::Cookies::CookieOverflow というエラーが発生します。注意。)

app/controllers/users フォルダ自体が無い時

app/controllers/users フォルダ自体が無い時は、Deviseのコントローラをカスタマイズするための前準備 rails generate devise:controllers users がされていないと思うので実行。
参照: Configuring controllers

$ rails generate devise:controllers users

これで app/controllers/users/omniauth_callbacks_controller.rb が生成されます。

※上記コマンドを実行すると下記のメッセージが出てくると思いますが、既にomniauth用に書き換え済みなので、このタイミングで実行した場合は不要。

===============================================================================

Some setup you must do manually if you haven't yet:

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

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

===============================================================================

で、Controllerをカスタマイズするとデフォルトのdevise の viewも使われなくなるので、カスタマイズ用のviewも作成します。
参照: Configuring views (さっきの一つ前の項)

$ rails generate devise:views users

(ほんとにログインだけしか使わないのであれば rails generate devise:views users -v sessions としてsessionだけ生成すればいいのかな…?とりあえず全部入りで作成してしまってます。)

生成したusersフォルダ配下のビューを優先的に使うように設定を変更します。

# config/initializers/devise.rb
  
# 元々コメントアウトされている config.scoped_views = false があるので
# そこをコメント解除してtrueに
  config.scoped_views = true

app/models/user.rb

先ほどコントローラに追加した google_oauth2 アクション内で使われている from_omniauth メソッドをUserモデルに追加します。

# app/models/user.rb

  class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable,
         omniauth_providers: [:google_oauth2]

##### 中略 #####

  protected
  # 以下を追加
    def self.from_omniauth(access_token)
      data = access_token.info
      user = User.where(email: data['email']).first

      # Uncomment the section below if you want users to be created if they don't exist
      unless user
          user = User.create(name: data['name'],
             email: data['email'],
             password: Devise.friendly_token[0,20]
          )
      end
      user
    end
end

(User modelにnameカラムが無いとエラーになります。注意。)

今回はユーザーがいなかったら自動で作ってほしいので、「Uncomment the section below …」以下のコメントを解除しています。

爆速ッ!! gem omniauth-google-oauth2 で認証させる - Qiita を見るとprotectedにした方が良いのかな、と思ったのでprotectedにしています。

参考: Ruby の private と protected 。歴史と使い分け - Qiita

Viewにリンクを追加

あとはViewにGoogleログイン用のリンクを設置します。

と言っても、実はUserモデルを :omniauthable に設定したことにより、Deviseが元々用意してくれているViewではすでに表示されるようになっています。

下記の箇所。

<%#  app/views/users/shared/_links.html.erb %>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
  <% end %>
<% end %>

自分でリンクを好きな場所に表示させたいなら、omniauth-google-oauth2のREADMEの通り下記を追加すればOK。

<%# html.erb %>
<%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path %>

コールバックのパスも確認できるようになっているので、Google Developer Consoleの「承認済みのリダイレクト URI」の設定が正しいか確認できます。
例えば下記の場合は承認済みのリダイレクトURIは「http://localhost:3000/users/auth/google_oauth2/callback」となる。

設定が正しいことが確認できたら、リンクをクリックするとGoogleの認証画面が表示されます。

そしてログインに成功すると「Google アカウントによる認証に成功しました。」と表示されます。(deviseの設定をしていれば。)

以上で設定は完了です!

  • Google認証の追加前に既にGoogleアカウントのメールアドレスでユーザーを作成していた場合、同じメールアドレスのGoogleアカウントで認証すれば同じユーザーとしてログインできます。
  • 逆に、Google認証でサインアップ後、(Railsアプリ上のユーザの)メールアドレスを変更すると、次回認証時に新しいユーザーと判定されて別のユーザーが作成されてしまいます。( from_omniauth の中でemailをキーにして既存ユーザーかどうか判定しているので。)

このあたりも好きに挙動変えられるはず。

0 件のコメント:

コメントを投稿