【Rails】Devise + OmniAuthでGitHubのOAuth認証を導入する
この記事では、DeviseとOmniAuthを使ってGitHubのOAuth認証を導入する手順を解説します。
Deviseのユーザ認証機能を持つRailsアプリに「GitHubでログイン」する機能を追加します。
OAuthの概要については、以下の記事をご参照ください。
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アプリの登録を行います。
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アプリの登録が完了したら、「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_path
とuser_github_omniauth_callback_path
が追加されます。
この時点で/users/sign_in
のSign in with 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
request.env["omniauth.auth"]
にプロバイダが提供する情報のハッシュが格納されている@user
が保存されていれば、sign_in_and_redirectメソッドでサインインしてafter_sign_in_path_for
にリダイレクトされる- sign_in_and_redirectメソッド:lib/devise/controllers/helpers.rb
- set_flash_messageメソッド:app/controllers/devise_controller.rb
- failureメソッド:app/controllers/devise/omniauth_callbacks_controller.rb
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
- find_or_create_by!
- friendly_tokenメソッド:lib/devise.rb
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
- How To: Allow users to edit their account without providing a password · heartcombo/devise Wiki
- app/controllers/devise/registrations_controller.rbのupdate_resourceメソッドはユーザの更新時に呼び出されるメソッドで、元のupdate_resourceメソッドはupdate_with_passwordメソッドを呼び出します。これをプロフィール編集時にパスワード入力がなければupdate_without_passwordメソッド(lib/devise/models/database_authenticatable.rb)を呼び出すようにオーバーライドしています。
params.except('current_password')
でcurrent_password
を除くことでエラーを回避しています
【参考】
- omniauth/omniauth: OmniAuth is a flexible authentication system utilizing Rack middleware.
- OmniAuth: Overview · heartcombo/devise Wiki
- omniauth/omniauth-github: GitHub strategy for OmniAuth
- cookpad/omniauth-rails_csrf_protection: Provides CSRF protection on OmniAuth request endpoint on Rails application.
- Devise 4.9をインストールしてRails 7.0 (Hotwire/Turbo)に対応する #Ruby - Qiita
- RailsでDeviseとOmniAuthによるTwitter/FacebookのOAuth認証、および通常フォーム認証を併用して実装 | EasyRamble
- 【Rails】omniauthを使ってgithub認証を実装する - No day younger than today