あまブログ

ドキドキ......ドキドキ2択クイ〜〜〜〜〜〜〜ズ!!

【Rails】Devise + OmniAuthでGitHubのOAuth認証を導入する

この記事では、DeviseとOmniAuthを使ってGitHubのOAuth認証を導入する手順を解説します。

Deviseのユーザ認証機能を持つRailsアプリに「GitHubでログイン」する機能を追加します。

OAuthの概要については、以下の記事をご参照ください。

qiita.com

qiita.com

qiita.com

1. 開発環境

  • Ruby:3.2.2
  • Rails:7.0.8
  • devise:4.9.3
  • omniauth:2.1.1
  • omniauth-github:2.0.1
  • omniauth-rails_csrf_protection:1.0.1
  • dotenv-rails:2.8.1
  • MacBook Pro (13-inch, 2020)
  • macOS Sonoma 14.2.1

2. 手順

2-1. サンプルアプリの作成

まずはdeviseのユーザ認証ができる簡単なサンプルアプリを作成します。

rails newを実行。

$ rails new devise-omniauth_app

次にdeviseのセットアップを行います。

Gemfileに以下を追加。

+ gem 'devise'

gemをインストール。

$ bundle install

以下のコマンドを実行してdeviseの設定ファイルを生成します。

$ rails generate devise:install

config/environments/development.rbに以下を追加して、DeviseメーラーのデフォルトURLオプションを設定します。

Rails.application.configure do
  省略
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

次にUserモデルを作成します。

以下のコマンドを実行してdeviseモジュールが設定されたUserモデルを作成します。

$ rails generate devise user
$ rails db:migrate

次にユーザ一覧・ユーザ詳細画面を作成します。

以下のコマンドを実行して、UsersControllerを作成します。

$ rails generate controller users index show --skip-routes --no-helper --no-assets --no-test-framework

作成されたapp/controllers/users_controller.rbを以下のように編集します。

class UsersController < ApplicationController
  def index
+   @users = User.all
  end

  def show
+   @user = User.find(params[:id])
  end
end

config/routes.rbにルーティングを追加します。

Rails.application.routes.draw do
  devise_for :users
+ root to: 'users#index'
+ resources :users, only: %i(index show)
end

ビューを編集します。

app/views/users/index.html.erb

<h1>ユーザの一覧</h1>

<div>
  <% @users.each do |user| %>
    <div>
      <p>
        <strong>Eメール:</strong>
        <%= user.email %>
      </p>
      <p>
        <%= link_to 'このユーザを表示', user %>
      </p>
    </div>
  <% end %>
</div>

app/views/users/show.html.erb

<h1>ユーザの詳細</h1>

<div>
  <p>
    <strong>Eメール:</strong>
    <%= @user.email %>
  </p>
</div>

<nav>
  <% if current_user == @user %>
    <%= link_to 'このユーザを編集', edit_user_registration_path %> |
  <% end %>
  <%= link_to 'ユーザの一覧に戻る', users_path %>
</nav>

app/views/layouts/application.html.erb

<body>
  <% if user_signed_in? %>
    <ul>
      <li>
        <%= link_to 'アカウント編集', edit_user_registration_path %>
      </li>
      <li>
        <%= link_to 'ログアウト', destroy_user_session_path, data: { turbo_method: :delete } %>
      </li>
    </ul>
  <% end %>
  <% if notice.present? %>
    <p id="notice"><%= notice %></p>
  <% end %>
  <% if alert.present? %>
    <p id="alert"><%= alert %></p>
  <% end %>
  <%= yield %>
</body>

最後にapp/controllers/application_controller.rbを以下のように編集します。

class ApplicationController < ActionController::Base
  # 未ログイン時に各ページにアクセスできなくする
  before_action :authenticate_user!

  private

  # サインイン後に/users/:idにリダイレクト
  def after_sign_in_path_for(_resource_or_scope)
    user_path(current_user)
  end

  # サインアウト後に/users/sign_inにリダイレクト
  def after_sign_out_path_for(_resource_or_scope)
    new_user_session_path
  end

  # アカウント編集後に/users/:idにリダイレクト
  def signed_in_root_path(_resource_or_scope)
    user_path(current_user)
  end
end

2-2. OAuthアプリの登録

次にGitHub上でOAuthアプリの登録を行います。

OAuth アプリの作成 - GitHub Docs

GitHubにログイン
↓
画面右上のユーザアイコンをクリックして「Settings」を選択
↓
左側のサイドバーで「Developer settings」を選択
↓
左側のサイドバーで「OAuth Apps」を選択
↓
「New OAuth App」を選択

OAuthアプリ登録フォームが表示されるので、以下の項目を入力して「Register application」を選択します。

  • Application name
    • 任意のアプリ名
  • Homepage URL
    • http://localhost:3000/
  • Authorization callback URL
    • http://localhost:3000/users/auth/github/callback

OAuthアプリ登録フォーム

OAuthアプリの登録が完了したら、「Client ID」と「Client secrets」を取得します。

次に、「Client ID」や「Client secrets」のような機密情報を安全に管理するためのgemである、dotenv-rails gemをインストールします。

Gemfileに以下を追加。

group :development, :test do
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
+ gem 'dotenv-rails'
end

gemをインストール。

$ bundle install

アプリのルートディレクトリに.envファイルを作成します。

$ touch .env

.envファイルに先ほど取得したClient IDとClient secretsの値を追加します。

+ GITHUB_KEY = <Client ID>
+ GITHUB_SECRET = <Client secrets>

機密情報を含む.envファイルをGitHubにpushしてしまうと致命的なセキュリティ問題に発展するので、.gitignore.envを追加します。

+ .env

最後に、deviseの設定ファイルにプロバイダの情報を追加します。

config/initializers/devise.rbに以下を追加。

Devise.setup do |config|
  省略
+ config.omniauth :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET']
end

設定されたプロバイダーの情報を確認します。

$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001> Devise.omniauth_providers
=> [:github]
irb(main):002> Devise.omniauth_configs
省略

2-3. GitHubログイン機能を実装

次にGitHubログイン機能を実装していきます。

まずは必要なgemをインストールします。

Gemfileに以下を追加。

+ gem 'omniauth'
+ gem 'omniauth-github'
+ gem 'omniauth-rails_csrf_protection'

gemをインストール。

$ bundle install

次にUserモデルにproviderカラムとuidカラムを追加します。providerカラムにはOmniAuthの認証で使用するプロバイダ名(github)が入り、uidカラムにはprovider毎に与えられるユーザ識別用の文字列が入ります。

以下のコマンドを実行して、マイグレーションファイルを生成。

$ rails g migration AddOmniauthToUsers provider:string uid:string

生成されたマイグレーションファイルを以下のように編集します。 db/migrate/[timestamp]_add_omniauth_to_users.rb

class AddOmniauthToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :provider, :string, null: false, default: ''
    add_column :users, :uid, :string, null: false, default: ''

    add_index :users, %i[provider uid], unique: true
  end
end

ここではproviderカラムとuidカラムの組み合わせに対してユニーク制約を付けています。 またそれぞれのカラムにNOTNULL制約とデフォルト値に空文字を指定しています。

マイグレーションを実行。

$ rails db:migrate

次に、DBの制約に合わせてモデル側にもユニークバリデーションを追加します。

app/models/user.rbを以下のように編集。

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  
+ validates :uid, uniqueness: { scope: :provider }
end

次に、GitHubログインボタンをクリックしてGitHub認証画面に遷移するように修正していきます。

Userモデルにomniauthableを追加します。

app/models/user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
+        :omniauthable, omniauth_providers: %i[github]

  validates :uid, uniqueness: { scope: :provider }
end

これによりuser_github_omniauth_authorize_pathuser_github_omniauth_callback_pathが追加されます。

この時点で/users/sign_inSign in with GitHubボタンをクリックするとGitHubのユーザ認証画面に遷移します。

GitHubのユーザ認証画面

認証後の処理はまだ実装していないためAuthorizeボタンをクリックするとRailsのエラー画面が表示されます。

次に、認証後のcallback処理を追加していきます。

config/routes.rbにcallback処理のルーティングを追加します。

Rails.application.routes.draw do
+ devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  root to: 'users#index'
  resources :users, only: %i(index show)
end

app/controllers/users/omniauth_callbacks_controller.rbを作成し、callback処理を追加します。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  skip_before_action :verify_authenticity_token, only: :github

  # プロバイダ名と同名のメソッドを定義する
  def github
    # from_omniauthメソッドはUserモデルに定義
    @user = User.from_omniauth(request.env["omniauth.auth"])

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

  def failure
    redirect_to root_path
  end
end

app/models/user.rb

class User < ApplicationRecord
  省略

  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
end

2-4. OAuth認証導入後の問題を解決する

OAuth認証の導入が完了しましたが、現状2つの問題が残っています。

  • メールアドレスとパスワードでサインアップするユーザを2人以上登録できない
  • 現在のパスワード入力なしでプロフィールを編集することができない(GitHubでログインしたユーザのプロフィールを編集できない)

ここからは、これらの問題を解決していきます。

まずは、メールアドレスとパスワードでサインアップするユーザを2人以上登録できるようにします。

メールアドレスとパスワードでサインアップするユーザを2人以上登録できない原因は以下です。

  • メールアドレスとパスワードでのサインアップの場合、providerカラムとuidカラムには空の値が入る
  • providerカラムとuidカラムの組み合わせに対するユニーク制約により、providerカラムとuidカラムが共に空の値のユーザは1人しか作成できない

上記の解決策として、メールアドレスとパスワードでのサインアップ時にuidカラムにランダムな値を格納するように修正していきます。

config/routes.rbにルーティングを追加。

Rails.application.routes.draw do
  devise_for :users, controllers: { 
    omniauth_callbacks: 'users/omniauth_callbacks',
+   registrations: 'users/registrations'
  }
  省略
end

app/controllers/users/registrations_controller.rbを作成し、以下のように編集します。

class Users::RegistrationsController < Devise::RegistrationsController
  protected

  def build_resource(hash = {})
    hash[:uid] = User.create_unique_string
    super
  end
end

app/models/user.rb

class User < ApplicationRecord
  省略

+ def self.create_unique_string
+   SecureRandom.uuid
+ end
end
  • app/controllers/devise/registrations_controller.rbのbuild_resourceメソッドはユーザ登録時に呼び出されるメソッドで、このメソッドをオーバーライドして元の処理の直前にhash[:uid]を追加しています。hash[:uid]にはSecureRandom.uuidによってランダムな値が格納されています。

次に、現在のパスワード入力なしでプロフィールを編集できるようにします。

app/controllers/users/registrations_controller.rbに以下を追加します。

class Users::RegistrationsController < Devise::RegistrationsController
  protected

  def build_resource(hash = {})
    hash[:uid] = User.create_unique_string
    super
  end

+ def update_resource(resource, params)
+   return super if params[:password].present?

+   resource.update_without_password(params.except('current_password'))
+ end
end

【参考】