Rails
devise
Rails5
omniauth-google-oauth2

メアド会員登録はなんかおっくうだから、devise + omniauth-google-oauth2でGoogle認証一本化。

やりたいこと

google認証でログインを実装したい!ときのメモ。
(メールアドレスの会員登録&ログインとかは色々と考慮も面倒だし使いたくない!)

プロジェクトを作成

なにはともあれプロジェクトを作成

$ rails new sample_auth
$ cd sample_auth

google認証するためのgemをインストール

deviseomniauth-google-oauth2を使います。
あと環境変数を設定したいのでdotenvを入れておきます。

Gemfile
gem 'devise'
gem 'omniauth-google-oauth2'
gem 'dotenv-rails'
$ bundle install

deviseの設定

$ rails g devise:install

色々初期設定を促されますが、置いといてUserモデルを作成。

$ rails g devise User

UserはGoogleのuidをキーに管理しようと思うのでマイグレーションファイルを書き換えます。拡張性(TwitterやFacebookなどでの認証を増やしたり)を考えてprovider項目を追加しています。

XXXXXXXXXXXXXX_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string   :uid
      t.string   :provider
      t.timestamps null: false
    end
  end
end
$ rails db:create
$ rails db:migrate

Google APIの設定

クライアントIDとクライアントシークレットを払い出します。
https://ablogcms.io/hands-on/snsLogin.html を参考にしました。
認証済みのURIは「 http://localhost:3000/users/auth/google_oauth2/callback 」な感じ。

上で発行したクライアントIDとクライアントシークレットはdotenvで管理します。

$ touch .env.development

開発環境(ローカル)で試したいので.env.developmentファイルを作成しましたが、商用環境で利用する場合は「.env.production」に記載します。

.env.development
GOOGLE_CLIENT_ID=XXXXXXXXXX #発行したクライアントID
GOOGLE_CLIENT_SECRET=XXXXXXXXXX #発行したクライアントシークレット

ついでにgitignoreに追加しておきましょう。

.gitignore
.env.development

omniauth-google-oauth2の設定

Googleクライアントキーとシークレットを設定します。

config/initializer/devise.rb
Devise.setup do |config|
  require 'devise/orm/active_record'
  config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], {}
end

Userモデルをomniauthに対応させます。(そして自前会員登録系を消しときます。)

app/models/user.rb
class User < ApplicationRecord
  devise :omniauthable, omniauth_providers: [:google_oauth2]
end

認証後のコールバック処理を実装

Userモデルに「from_omniauth」methodを作成します。これはコールバック用のControllerで利用します。

app/models/user.rb
class User < ApplicationRecord
  devise :omniauthable, omniauth_providers: [:google_oauth2]

  def self.from_omniauth(token)
    user = User.where(provider: token.provider).find_by(uid: token.uid)
    user = User.create(uid: token.uid, provider: token.provider) unless user
    user
  end

end

self.from_omniauth(token)の中身は
- 1行目:provider, uidが一致するユーザーをDBからSELECT
- 2行目:一致するユーザーがいない場合はDBに新規登録
- 3行目:userをリターン
といった感じです。

今回はuidとproviderしか使っていませんが、ユーザー情報は色々なものが取れます。
=> https://github.com/zquestz/omniauth-google-oauth2#auth-hash

次にコールバック用のControllerを作っていきます。deviseのControllerをカスタマイズしたいので次のコマンドでControllerを作成します。

$ rails g devise:controllers users

コールバック用のControllerであるusers/omniauth_callbacksをカスタマイズしていきます。
他のは使わないので消しちゃってもいいです。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def google_oauth2
    @user = User.from_omniauth(request.env['omniauth.auth'])
    sign_in_and_redirect @user, event: :authentication
  end
end

3行目でUserモデルのfrom_omniauthメソッドを利用して認証情報からユーザーを取得しています。
そして4行目でログイン処理を実行しています。

最後に今作成したControllerが動くようにルーティングを設定してきます。

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

Google認証を試してみる!

やりたいことは
1. after_loginページはログイン前はアクセスできない(before_loginページにリダイレクトされる)
2. before_loginページからGoogle認証後、after_loginページに遷移できる
3. after_loginページでログアウトできる
です!

まず、before_loginページとafter_loginページを作ります。

$ rails g controller pages before_login after_login

1. after_loginページはログイン前はアクセスできない

before_loginページをroot_pathに設定して、ログイン前はroot_pathしかアクセスできないようにします。
まずはroot to:でbefore_loginページをroot_pathに設定。

config/routes.rb
Rails.application.routes.draw do
  get 'pages/after_login'
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

  root to: 'pages#before_login'
end

次にApplication Controllerにdeviseのauthenticate_user!をbefore_actionとして定義してログイン前はどのアクションも実行できないようにしちゃいます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end

このままだとbefore_loginページにもアクセスできないので、before_loginアクションはskip_before_actionします。

app/controllers/pages_controller.rb
class PagesController < ApplicationController
  skip_before_action :authenticate_user!, only: [:before_login]

  def before_login
  end

  def after_login
  end
end

ここまででログイン前にafter_loginページにアクセスするとbefore_loginページにリダイレクトされるようになっているはずです。

2. before_loginページからGoogle認証後、after_loginページに遷移できる

Googleのログインボタンは公式サイトからダウンロードできます。
https://developers.google.com/identity/branding-guidelines?hl=ja

今回は「btn_google_signin_dark_normal_web.png」を「app/assets/images/」直下に置いてます。
before_loginページでこのボタンをクリックするとGoogle認証ページに遷移するようにします。

app/views/auths/before_login.html.erb
<h1>Pages#before_login</h1>
<p>Find me in app/views/pages/before_login.html.erb</p>
<% unless user_signed_in? %>
  <%= link_to user_google_oauth2_omniauth_authorize_path do %>
    <%= image_tag "btn_google_signin_dark_normal_web.png" %>
  <% end %>
<% end %>

user_signed_in? はログイン済みの場合にtrueを返してくれるdeviseのhelperで、未ログインの場合のみボタンを表示するようにしてます。

ログインしたらafter_loginページに遷移するようにomniauth_callbacks_controller.rbも書き換えます。

app/controllers/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def google_oauth2
    @user = User.from_omniauth(request.env['omniauth.auth'])
    sign_in @user, event: :authentication
    redirect_to pages_after_action_path
  end

end

これでGoogle認証でログインできるようになる!

3. after_loginページでログアウトできる

deviseのログアウト機能を使えるようにします。

config/routes.rb
Rails.application.routes.draw do
  get 'pages/before_login'
  get 'pages/after_login'
  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

  root to: 'pages#before_login'
end

devise_scope内でSessionsControllerのdestroyメソッドを利用。
あとはafter_loginページにログアウトのリンクを設定してあげるだけ。

app/views/auths/after_login.html.erb
<h1>Auths#after_login</h1>
<p>Find me in app/views/auths/after_login.html.erb</p>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>

おまけ:退会

ついでに退会も作っておこうと思います。退会できないサービスとか、ちょっと怖くなっちゃうので。
退会はRegistrationsControllerのdestroyアクションです。

config/routes.rb
Rails.application.routes.draw do
  get 'pages/before_login'
  get 'pages/after_login'
  devise_for   :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  devise_scope :user do
    delete 'sign_out',    to: 'devise/sessions#destroy', as: :destroy_user_session
    delete 'resignation', to: 'devise/registrations#destroy', as: :user_resignation
  end

  root to: 'pages#before_login'
end
app/views/auths/after_login.html.erb
  <h1>Auths#after_login</h1>
  <p>Find me in app/views/auths/after_login.html.erb</p>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
  <br>
  <%= link_to "退会", user_resignation_path, method: :delete %>

以上です!

Reference