はじめに
【Rails】Twitterクローンを作ってみた 〜画像アップロード(ActiveRecord)〜の続きです。こちらを前提に進めていきますので、まだご覧になっていない方はそちらからご覧ください!※こちらはdockerでの環境構築を省きます。
サービス環境
- ruby 3.0.0
- Rails 6.0.4
- docker
- mysql 8.0.2
- Slim, SCSS
OmniAuth
条件
- Git Hubアカウントでログインできる
DeviseのモジュールOmniauthableを使用しGit Hubログインをできるようにしてください。
実装流れ(簡略)
-
https://fuga-ch85.hatenablog.com/entry/2021/04/04/164302
こちらを参考にGitHubのClient IDとClient secretsを取得してください。 - Client IDとClient secretsを.envファイルに保存。.gitignoreに.envファイル追記。
(.env.exampleファイルを作り、どの環境変数を設定すれば良いかわかるようにすると親切) - Gemファイルに
gem 'omniauth-github'
gem 'omniauth-rails_csrf_protection'
を追記し、bundle install
- マイグレーションファイル作成
-
db:migarate
→失敗→db:reset
→db:migrate
- config/initializers/devise.rbでomniauthの設定をする
- ルーティング作成
- models/user.rbでユーザー登録のメソッド作成
- controllers/users/omniauth_callbacks_controller.rbでGitHubログインのメソッド作成
- controllers/users/registrations_controller.rbでパスワードなしでのアカウント更新を可能にするメソッド作成
- ルーティング作成
- サインアップ画面(html, css)の修正
サインアップ画面
GitHubサインイン後の画面
解説
マイグレーションファイルの作成
class AddOmniauthToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :uid, :string
add_column :users, :provider, :string, null: false, default: ""
add_index :users, [:uid, :provider], unique: true
end
end
uidとproviderカラムを作成します。uid は oAuth を使用してログインしたユーザーの一意の ID で構成されます。uidにnull: false
を追記していない理由は通常ログインの際にuidが無くともログインできるようにするためです。エラー原因の1つになる、とても重要なものになります。そしてuserモデルにバリデーションを加えるのを忘れずにしましょう!
models/user.rb
class User < ApplicationRecord
...
validates :uid, uniqueness: { scope: :provider }, if: -> { uid.present? }
# authの中身はGitHubから送られてくる大きなハッシュ。この中に名前やメアドなどが入っている。
# providerカラムとuidカラムが送られてきたデータと一致するユーザーを探す。
# もしユーザーが見つからない場合は新規作成する。
def self.find_for_github_oauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create! do |user|
# 名前を取得するときはこのように書く(今回はUserモデルにname属性がないのでエラーなる)
# user.name = auth.info.name
user.email = auth.info.email
# 任意の20文字の文字列を作成する
user.password = Devise.friendly_token[0, 20]
end
end
...
end
ここで重要になるものがif条件です。uidが存在していないときにこのバリデーションが作用するとまたしても通常ログインの際エラーが出てログインできなくなります。私はここのif条件を忘れてしまっていたため、エラー地獄のひどい目に遭いました笑
さらにGitHubからもらったデータでユーザー登録するメソッドを作ります。しかしながらこの状態だとGitHubログインした際、プロフィール編集ができません。なぜならプロフィール編集に現在のパスワードが必要になっています。しかし現在のパスワードはGitHubのパスワードではなくこのメソッドで作ったランダムのパスワードです。つまりGitHubでログインしたユーザーはプロフィール編集できない仕様になっています。この問題を後ほどのcontrollers/users/registrations_controller.rbで解決していきます!
db:migrateでのエラー
userテーブルにデータが入っているとエラーが出ます。なぜこのようなことが起きたのかというと、先程のマイグレーションファイルでuidに対してnull: false
を追記してしまったままであったからです。これは現データのuser.uidに対してNOT NULL制約がかかってしまったためでした。その場ではrails db:reset
をして現データを削除してからrails db:migrate
することにより解決できました。
ルーティング(routes.rb)
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: "users/registrations",
omniauth_callbacks: "users/omniauth_callbacks"
}
...
end
ハッシュロケットは可能な限り使わないようにしましょう!
controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# You should configure your model like this:
# devise :omniauthable, omniauth_providers: [:github]
# You should also create an action method in this controller like this:
def github
# request.env["omniauth.auth"]にGitHubから送られてきたデータが入っている
@user = User.find_for_github_oauth(request.env["omniauth.auth"])
unless @user.persisted? # ログイン失敗
session["devise.github_data"] = request.env["omniauth.auth"]
return redirect_to new_user_registration_url
end
if @user.profile.blank? # プロフィール登録画面への誘導
sign_in @user, event: :authentication
return redirect_to new_profile_path
end
sign_in_and_redirect @user, event: :authentication #プロフィールを作成していれば、ログイン
set_flash_message(:notice, :success, kind: "Github") if is_navigational_format?
end
# More info at:
# https://github.com/heartcombo/devise#omniauth
# GET|POST /resource/auth/github
# def passthru
# super
# end
# GET|POST /users/auth/github/callback
# def failure
# super
# end
# protected
# The path used when OmniAuth fails
# def after_omniauth_failure_path_for(scope)
# super(scope)
# end
end
コメントアウトしてあるsuperに関してメソッドを追記しない場合はコメントアウトのままで大丈夫です。
controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
...
protected
def update_resource(resource, params)
return super if params["password"]&.present?
# 現在のパスワードなしでアカウントの更新をする
resource.update_without_password(params.except("current_password"))
end
end
上記コードによりランダムなパスワードが振り分けされても(GitHubのパスワードで無くとも)プロフィール詳細を編集できるようになりました!
.env.exampleファイル
GITHUB_ID =
GITHUB_SECRET =
最後に、どの環境変数を設定すれば良いかわかるようにしておくと親切です。※秘匿情報は書かない
実装を通して学んだこと
1. uidとproviderカラム
omniauthを使ったログインではこの二つのカラムをセットにして考えるということを学びました。uid は oAuth を使用してログインしたユーザーの一意の ID で構成されることから、通常ログインでは絶対に絡めないようにすること。validationで必ずif条件によりuidが存在するかどうか判断してproviderカラムにユニーク制約をかけること(if条件をつけないと通常ログインでもユニーク制約がかかり、エラーが発生する)。
2. omniauthでのパスワード問題
GitHubログインをした際は、その時点で適当なパスワードを振るように設定することと、パスワードが必要な操作をする際にパスワードなしで、操作できるようにコントローラの編集が必要なことを学びました。
3. db:migarate失敗
db:reset
からのdb:migrate
で解決。
感想
今回は非常に時間がかかり、苦労しました。まずはomniauth?oAuth?というとこから始まりました笑
GitHubログインができた!と喜んでいると、通常のログインが機能しなくなっていたり、てんてこまいになりましたが解説の通り、マイグレーションファイルやモデルのバリデーションをいじることによりなんとか解決できました。実際にこの章を実装してみるとよくわかりますがとても得られるものが多いなと感じました。特にマイグレーションファイルのNOT NULL制約であったり、モデルのバリデーション機能に関してはとても良い復習になりました。是非実装してみてください!
次回はツイートのいいね機能についてアウトプットしていこうと思います!読んでいただきありがとうございました。