パスワードリセット機能の実装

ユーザー登録ができるサービスには必ず付いていると言っても過言ではないであろう機能、パスワードリセット機能を実装していきます。

今回はgem sorceryのモジュールであるreset_passwordを導入します。sorceryの導入を前提で進めていきます。

パスワードリセットの流れ

  • パスワードリセット申請ページからメールアドレスを送信
  • メールを受信しリンクをクリック
  • パスワードリセットページでパスワードを更新

という感じです。
開発環境ではメールを実際には送らないようletter_opener_webというgemを使ってますがそちらは割愛させていただきます。
にではいってみましょう。

reset_passwordモジュールのインストール

$ rails g sorcery:install reset_password --only-submodules

でインストール。
マイグレーションファイルが作成されます。
トークンは一意でなければいけないのでreset_password_tokenカラムにindex制約をかけておきます。 トークンとはワンタイムパスワードのようなものです。

class SorceryResetPassword < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :reset_password_token, :string, default: nil
    add_column :users, :reset_password_token_expires_at, :datetime, default: nil
    add_column :users, :reset_password_email_sent_at, :datetime, default: nil

    add_index :users, :reset_password_token # これを追加
  end
end

として

rails db:migrate

DBに反映。

モデルの方でもvalidationを設定しておきます。

# user.rb

validates :reset_password_token, uniqueness: true, allow_nil: true

allow_nilオプションは、対象の値がnilの場合にバリデーションをスキップします。
パスワードが変更される際にreset_password_tokenは削除されnilになります。
allow_nil: trueがないとユニーク制約にに引っかかってしまうのでこちらも設定します。

Mailerの作成

メイラーとはRailsからメールを送信する機能です。
まずは

$ rails g mailer UserMailer reset_password_email

でUserMailerを作成します。
rails g mailer メーラー名 メソッド名です。
共通の設定

# application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  default from: 'example@example.com' # メールの送信元
  layout 'mailer' # mailer.html.erb、mailer.text.erbをメールのレイアウトとして使用する
end


メール送信のメソッドを定義します。

# user_mailer.rb

class UserMailer < ApplicationMailer
  def reset_password_email(user)
    @user = User.find(user.id)
    @url = edit_password_reset_url(@user.reset_password_token)
    mail to: user.email,
         subject: 'パスワードリセット'
  end
end

mailメソッド送信先、メールのタイトルを設定しています。

sorcery.rbの設定もしておきます。

Rails.application.config.sorcery.submodules = [:reset_password] # サブモジュールとしてreset_passwordを設定

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.reset_password_mailer = UserMailer # パスワードリセット用のメーラーにUserMailerを指定
  end
end

メール本文を作成

views/user_mailer/reset_password_email.text.erbviews/user_mailer/reset_password_email.html.erbに実際のメールの内容を記述します。
一部のメールソフトではhtml形式のメールが受け取れない(ガラケーとか?)などがあるようで、その対策としてtext形式とhtml形式の2つで作成します。
Railsではマルチパートメールという仕組みでtext形式とhtml形式の両方をメール送信して、メールクライアント側で自動判別してメールを表示してくれるそうです。
また、メンテナンス等の際、両方ともメンテしなくてもいいようにactionmailer-textというgemもあるようです。
ここでは取り扱わないので興味のある方は調べてみてください。

コントローラーの設定

# app/controllers/password_resets_controller.rb

class PasswordResetsController < ApplicationController
  skip_before_action :require_login
    
  def create 
    @user = User.find_by_email(params[:email])
        
    @user.deliver_reset_password_instructions! if @user
        
    redirect_to(root_path, :notice => 'Instructions have been sent to your email.')
  end
    
  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    if @user.blank?
      not_authenticated
      return
    end
  end
      
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    if @user.blank?
      not_authenticated
      return
    end

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password(params[:user][:password])
      redirect_to(root_path, :notice => 'Password was successfully updated.')
    else
      render :action => "edit"
    end
  end
end

フォームの作成

パスワードリセット申請フォーム

<%= form_with url: password_resets_path, local: true do |f| %>
    <%= f.label :email, 'メールアドレス' %>
    <%= f.email_field :email, class: 'form-control' %>

  <%= f.submit class: 'btn btn-primary' %>
<% end %>

パスワードリセットフォーム

<%= form_with model: @user, url: password_reset_path(@token), local: true do |f| %>
    <%= f.label :email %><br>
    <%= @user.email %>

    <%= f.label :password %>
    <%= f.password_field :password, class: 'form-control' %>

    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit class: 'btn btn-primary ' %>
<% end %>

ルーティングの設定

resources :password_resets, only: %i[create edit update]

を追加。 以上です。

最後に

今まで使ったことなかったMailerとかが出てきて難しかったです。
githubからコピペしたのでコントローラ内のコードの流れが正直まだわかってません。 また振り返って流れを追いたいと思います。
間違いなどありましたらコメントいただけると幸いです。
読んでいただきありがとうございました。