deviseを使わない自前のユーザー認証 の続きで、今回はパスワードリセット機能を実装します。
下準備
1. カラムを追加します
$ rails g migration add_password_reset_to_users
20240726000000_add_password_reset_to_users.rb
class CreateUsers < ActiveRecord::Migration[7.1] def change add_column :users, :password_reset_sent_at, :datetime add_column :users, :password_reset_digest, :string end end
2. Userモデルに追記する
app/models/user.rb
def password_reset token = User.new_token self.password_reset_digest = User.digest(token) self.password_reset_sent_at = Time.zone.now self.save UserMailer.with(user: self, token: token).password_reset.deliver_now end def password_reset_expired? password_reset_sent_at < 10.minutes.ago end def password_update(params) self.password_reset_digest = nil self.password_reset_sent_at = nil self.update(params) end def authenticated?(attribute, token) digest = self.send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end class << self def digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end def new_token SecureRandom.urlsafe_base64 end end
3. メールを送信できるようにする(本記事では割愛)
パスワードリセットのリクエストを実装
1. ルーティングを設定する
config/routes.rb
get '/password_reset/request', to: 'password_reset/request#new' post '/password_reset/request', to: 'password_reset/request#create' get '/password_reset/requested', to: 'password_reset/request#index'
2. コントローラーの実装
app/controllers/password_reset/request_controller.rb
class PasswordReset::RequestController < ApplicationController skip_before_action :signed_in_user def new; end def create user = User.find_by(email: params[:email]) user&.password_reset redirect_to password_reset_requested_url, status: :see_other end def index; end end
3. viewsの実装
app/views/session/new.html.erb
以下を追記
<%= link_to "パスワードを忘れた", password_reset_request_path %>
app/views/password_reset/request/new.html.erb
<div>パスワードを再設定したいアカウントのメールアドレスを入力してください</div> <%= form_with url: password_reset_request_path do |f| %> <div> <label>メールアドレス</label> <%= f.email_field :email, placeholder: "user@example.com", required: true %> </div> <div> <%= f.submit "送信する" %> </div> <% end %>
app/views/password_reset/request/index.html.erb
<div>入力していただいたメールアドレス宛にパスワード再設定用のメールを送信しました。</div>
4. メーラーの実装
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer def password_reset @token = params[:token] mail(to: params[:user].email, subject: 'パスワード再設定') end end
app/views/user_mailer/password_reset.html.erb
<div>パスワード再設定依頼を受け付けました。</div> <div>10分以内に以下のURLより再設定を行なってください。</div> <div><%= link_to "パスワードを再設定する", password_reset_url(token: @token, email: @user.email) %></div>
パスワードの更新を実装
1. ルーティングを設定する
config/routes.rb
get '/password_reset/processed', to: 'password_reset#index' get '/password_reset/:token', to: 'password_reset#edit', as: 'password_reset' post '/password_reset/:token', to: 'password_reset#update'
2. コントローラーの実装
app/controllers/password_reset_controller.rb
class PasswordResetController < ApplicationController before_action :set_user, except: :index before_action :valid_user, except: :index skip_before_action :signed_in_user def index; end def edit; end def update if @user.password_update(user_params) redirect_to password_reset_processed_url, status: :see_other else render :edit, status: :unprocessable_entity end end private def user_params params.fetch(:user, {}).permit(:password, :password_confirmation) end def set_user @user = User.find_by(email: params[:email]) end def valid_user unless @user.present? && @user.authenticated?(:password_reset, params[:token]) && !@user.password_reset_expired? redirect_to password_reset_request_url, status: :see_other and return end end end
3. viewsの実装
app/views/password_reset/edit.html.erb
<%= form_with model: @user, url: password_reset_path(token: params[:token], email: params[:email]), local: true, method: :post do |f| %> <div> <label>パスワード</label> <%= f.password_field :password, placeholder: "英数字を2つ以上組み合わせよう", required: true %> </div> <div> <label>パスワード(確認)</label> <%= f.password_field :password_confirmation, placeholder: "英数字を2つ以上組み合わせよう", required: true %> </div> <div> <%= f.submit "更新する" %> </div> <% end %>
app/views/password_reset/index.html.erb
<div>パスワードを変更しました。</div>
終わり
以上で実装は終わりです!
必要に応じて各種文言などをカスタマイズしたり、エラーメッセージを出してみたりしてみてください!