[Rails]deviseによるユーザー認証 2/2
はじめに
前回の記事ではdeviseによるユーザー登録・ログイン機能ができましたので、編集機能も作成していきます。
- プロフィール編集(パスワードなし)
- パスワード編集(現在のパスワード+新しいパスワード)
- パスワードリセット
過去の記事でProfiles
、Passwords
、Password_resets
コントローラーでそれぞれ実装しましたがdevise
を使う場合のやり方もまとめてみました。
アプリに合わせてコードをカスタマイズすることがほとんどですね。
環境:
Rails 6.1.7.3
ruby 3.0.0
コントローラーを作成する
ユーザーCRUD関連のコントローラーを生成します。
omniauth_callbacks_controller.rb
をすでに作成したためスキップします。
bin/rails generate devise:controllers users
create app/controllers/users/confirmations_controller.rb
create app/controllers/users/passwords_controller.rb
create app/controllers/users/registrations_controller.rb
create app/controllers/users/sessions_controller.rb
create app/controllers/users/unlocks_controller.rb
skip app/controllers/users/omniauth_callbacks_controller.rb
routes.rb
を編集する
デフォルトのコントローラーをオーバーライドする必要があります。
Rails.application.routes.draw do
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations'
}
end
パスワードなしで編集できるようにする
Devise のデフォルトのユーザーの編集画面(registration#edit
)では、ログイン済みのユーザーが自分の情報を変更するために、現在のパスワードを入力するのが必須となっています。
これをパスワードなしで自分の情報を更新できるように変更します。
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update, :edit]
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:user_name])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:user_name, :email, :description, :profile, :profile_cache])
end
def update_resource(resource, params)
resource.update_without_password(params)
end
end
user.rb
にロジックを記述する
devise
のデフォルトのpassword_required?
メソッドを上書きします。
class User < ApplicationRecord
validates :password, presence: true, length: { minimum: 6 }, if: :password_required?
def password_required?
new_record? || password.present? || password_confirmation.present?
end
end
devise
のデフォルトの設定では次の条件のいずれかが満たされる場合にパスワードが必要となります。
!persisted?
:新規にユーザーを作成する時
!password.nil?
:パスワードがnil
ではない時
!password.confirmation.nil?
:パスワード確認がnil
ではない時
ユーザー編集用urlを設定する
<%= link_to User.human_attribute_name(:profile), edit_user_registration_path, class: 'nav-link' %>
ユーザー編集ビューを設定する
パスワードのフィールドを削除し、ユーザー名、紹介、プロフィール画像用のフィールドを追加します。
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :user_name %>
<%= f.text_field :user_name, class: "form-control mb-3" %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control mb-3" %>
</div>
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: "form-control mb-3" %>
</div>
<div class="form-group mb-3">
<%= f.label :profile %>
<%= f.file_field :profile, class: "form-control js-file-select-preview", accept: 'image/*', data: { target: '#preview-target' } %>
<%= f.hidden_field :profile_cache %>
</div>
<div class='form-group mb-3'>
<% if @user.profile.present? %>
<%= image_tag @user.profile.url, size: '100x100'%>
<% else %>
<%= image_tag 'user.png', id: 'preview-target', size:'50x50', class: 'round-circle' %>
<% end %>
</div>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
<h3>Cancel my account</h3>
<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
<%= link_to "Back", :back %>
パスワードなしでアカウントを編集および更新できることを確認します。
バリデーションに引っかかる場合エラーが出ることも確認します。
次パスワードの更新も見ていきます。
パスワードを更新する
devise
のwiki
では三つの方法を紹介されました。
registrations#edit
がプロフィールの編集と更新に使いましたので三つ目の方法で実装したいと思います。
仮にプロフィールの編集および更新は他のコントローラー(Profile
)で実装した場合、デフォルトのコントローラー(Devise::RegistrationsController#update
)でパスワードの更新を行うのが一番スムーズにできると思います。
Userコントローラーを作成する
class UsersController < ApplicationController
before_action :authenticate_user!
def edit
@user = current_user
end
def update_password
@user = current_user
if @user.update_with_password(password_params)
flash[:success] = "パスワードを更新されました。もう一度ログインお願いします。"
redirect_to new_user_session_path
else
flash.now[:danger] = "パスワードの更新が失敗しました。もう一度試してください。"
render :edit, status: :unprocessable_entity
end
end
private
def password_params
params.require(:user).permit(:password, :password_confirmation, :current_password)
end
end
bypass_sign_in(@user)
メソッドを使用して、パスワードが変更された後にユーザーを自動的に再ログインさせることができます。
ルーティングを指定する
パスをupdate_password
にしましたが他の名前でも大丈夫です。
コントローラーのアクション名と一致する必要があることに注意しましょう。
resource :user, only: [:edit] do
collection do
patch 'update_password'
end
end
update_with_password
は、Deviseによって提供されるメソッドであり、Rails アプリケーションでユーザーのパスワードを更新するために使用されます。通常、このメソッドは、ユーザーが現在のパスワードを提供して新しいパスワードを変更する場合に使用されます。
current_password
パラメーターは、ユーザーが現在のパスワードを提供するために使用されます。
update_with_password
メソッドはcurrent_password
とセットで使用する必要があります。
current_password
なしでパスワードを更新できるようにする場合は、いつも通りのupdate
メソッドで大丈夫です。
パスワード更新へのurlを追加する
<%= link_to t('users.edit.title'), edit_user_path, class: 'nav-link' %>
パスワードを更新用ビューを作成する
<h1><%= t('.title') %></h1>
<%= form_for(@user, :url => { :action => "update_password" } ) do |f| %>
<div class="form-group">
<%= f.label :current_password %>
<%= f.password_field :current_password, class: 'form-control mb-3' %>
</div>
<div class="form-group">
<%= f.label :password %><em>(<%= t('devise.shared.minimum_password_length', count: 6 ) %>)</em>
<%= f.password_field :password, :autocomplete => "off", class: 'form-control mb-3' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control mb-3' %>
</div>
<%= f.submit class: 'btn btn-primary' %>
<% end %>
パスワードの編集および更新できることを確認します。
バリデーションに引っかかる場合エラーが出ることも確認します。
ユーザーにメール通知を送る
パスワード更新後にユーザーにメール通知を送る場合はdevise
の設定を変えます。
app/views/devise/mailer/password_change.html.erb
のメールが送られます。
Devise.setup.do |config|
- # config.send_password_change_notification = false
+ config.send_password_change_notification = true
end
最後はパスワードのリセットを見ていきます。
パスワードリセット
devise
のパスワードリセット機能は:recoverable
のモジュールで行われ、reset_password_token
、reset_password_sent_at
、encrypted_password
のフィールドが必要です。
passwords#edit
コントローラーとpasswords/edit.html.erb
を通してリセット用のメールをリクエストします。
ユーザーは受け取ったメール内のリンクをクリックして、パスワードリセットページにアクセスします。このページでは、新しいパスワードを入力し、パスワードをリセットします。Deviseはデフォルトでnew_password_path
を提供しています。
パスワードリセットページでユーザーが新しいパスワードを入力すると、パスワードが更新されます。
今回devise
のデフォルトの機能をそのままで良いので流れだけを見ていきましょう。
:require_no_authentication
edit_user_password_path
にアクセスしてみると、:require_no_authentication
とのメソッドに引っかかりましたね。ログインした状態でパスワードを更新できないようになっています。
Started GET "/users/password/edit" for ::1 at 2023-07-17 11:15:46 +0900
Processing by Devise::PasswordsController#edit as HTML
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 15], ["LIMIT", 1]]
Redirected to http://localhost:3000/
Filter chain halted as :require_no_authentication rendered or redirected
Completed 302 Found in 4ms (ActiveRecord: 0.1ms | Allocations: 1464)
passwords_controller.rb
のソースコードを見てみると、prepend_before_action :require_no_authentication
という行がありました。
通常、before_action
メソッドは、アクションが実行される前に特定のメソッドを実行するために使用されます。prepend_before_action
メソッドは、他の before_action
メソッドよりも前に実行されるため、特定のアクションの前にこのメソッドを呼び出すことができます。
require_no_authentication
メソッドは、ユーザーが認証済みでない場合にのみアクションを実行することを要求します。つまり、ログインしていないユーザーのみがアクションにアクセスできるようになります。
これは、一部のアクションを非ログインユーザーに公開する必要がある場合に便利です。たとえば、新規登録ページやログインページなど、ログインしていないユーザーがアクセスできるようにしたいページで使用できます。
パスワードリセットメールの有効期間
デフォルトの有効期間では6時間となってます。
Devise.setup.do |config|
config.reset_password_within = 6.hours
end
パスワードリセットメール
メールを日本語にカスタマイズしてみます。
deviseのメールテンプレートはapp/views/devise/mailer
にあります。
reset_password_instructions.html.erb
を編集します。
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<p><%= @user.user_name %>様</p>
<p>
パスワードリセットのご申請ありがとございます。
下のリンクをクリックし、パスワードをリセットする画面が表示されますので、新しいパスワードを設定してください。
※パスワードリセットはメール受信後から「6時間以内」に行ってください。
</p>
<p><%= link_to "パスワードをリセットする", edit_password_url(@resource, reset_password_token: @token) %></p>
</body>
</html>
退会機能
Registrations#destroy
Deviseの退会機能はデフォルトでRegistrations
コントローラーのdestroy
アクションで実現できます。
デフォルトのビュー:
<h3>Cancel my account</h3>
<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
論理削除
上記の方法ではユーザーと紐付いた全てのデータも一緒に削除されますが、データを残す論理削除という扱いもあります。
公式ドキュメント:
終わりに
deviseによるユーザーのCRUDでした。
他の機能も少しずつ試していきたいと思います。
Discussion