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

概要

  • パスワードリセット申請画面にメールアドレスを入力し申請後、tokenを発行しデータベースへ保存させる
  • 入力されたメールアドレスにパスワードリセットページヘのリンクを送信し、発行したtokenをURLへ組み込み、ユーザーを判別する。
  • ユーザーはリンクからパスワードリセットページへ行き、新しいパスワードを入力し更新できる。

実装

sorceryのreset_passwordモジュールを導入する。

 rails g sorcery:install reset_password --only-submodules
        gsub  config/initializers/sorcery.rb
      insert  app/models/user.rb
      create  db/migrate/20210625080320_sorcery_reset_password.rb

マイグレーションファイルのテーブル名を編集し、migrateする。

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_column :users, :access_count_to_reset_password_page, :integer, default: 0

    add_index :users, :reset_password_token
  end
end
  
 $ rails db:migrate
== 20210625080320 SorceryResetPassword: migrating =============================
-- add_column(:users, :reset_password_token, :string, {:default=>nil})
   -> 0.0012s
-- add_column(:users, :reset_password_token_expires_at, :datetime, {:default=>nil})
   -> 0.0012s
-- add_column(:users, :reset_password_email_sent_at, :datetime, {:default=>nil})
   -> 0.0008s
-- add_column(:users, :access_count_to_reset_password_page, :integer, {:default=>0})
   -> 0.0010s
-- add_index(:users, :reset_password_token)
   -> 0.0015s
== 20210625080320 SorceryResetPassword: migrated (0.0065s) ====================

Userモデルにallow_nil: trueとユニーク制約をつける。

validates :reset_password_token, uniqueness: true, allow_nil: true

Action Mailerの作成

RailsではデフォルトでActionMailerというメール送信機能がある。
パスワードリセットに使用するUserMailerを作成。

$ rails g mailer UserMailer reset_password_email
Running via Spring preloader in process 5906
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/reset_password_email.text.erb
      create    app/views/user_mailer/reset_password_email.html.erb

サブモジュールを使用できるように定義を追加。
sorcery.rbのリセットパスワードメイラーにUserMailerを記述。
これでapp/mailers/user_mailer.rbとつながる。

  Rails.application.config.sorcery.submodules = [:reset_password]

# Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.reset_password_mailer = UserMailer 
  end

user_mailer.rbへパスワードリセット用のメソッドを記述。

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

@user = User.find user.idでユーザー情報を取得
reset_password_emailメソッドでuser_mailer/reset_password_email.html.erbがメールのフォーマットとして読み込まれる。
@url = edit_password_reset_url(@user.reset_password_token) mail(to: user.email,パスワード変更のurlと送信先アドレスを指定。

メールの差し出し元とレイアウトファイルを指定

app/views/layoutsのmailerファイルがレイアウトになる。

  class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end

メイラーのview設定

実際に送られてくるメール本文

  <h1>パスワードリセット</h1>
<p>
  <%= @user.last_name %><%= @user.first_name %> 様
  パスワード再発行のご依頼を受け付けました。
  こちらのリンクからパスワードの再発行を行ってください。
  <%= @url %>
</p>
  <%= @user.last_name %><%= @user.first_name %> 様
パスワード再発行のご依頼を受け付けました。
こちらのリンクからパスワードの再発行を行ってください。
<%= @url %>

letter_opener_webの追加

開発環境ではメールは送られないように設定する。
gemfileのdevelopmentに記載し、bundleする。

  group :development do
  gem 'letter_opener_web', '~> 1.0'
end

ルーティングの下へletter_opener_webにアクセスできる記述を追加。

mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?

development.rbへ設定を追加。

Rails.application.configure do
  config.action_mailer.perform_caching = false

  config.action_mailer.default_url_options = { host: 'localhost:3000' }
  config.action_mailer.delivery_method = :letter_opener_web
end

letter_openerでメールが送られるか確認。

f:id:meo2:20210630003157p:plain

環境変数の設定

gem configの導入
Gemfileへgem 'config'を記述。bundle後、以下のコマンドを実行。

$ rails g config:install

設定ファイルが生成される。

  • config/settings/development.yml
  • config/settings/production.yml
  • config/settings/test.yml
    定数の置き換え(すべてのファイルに定数を記述する。)
    今回はlocalhost:3000(本番環境にデプロイする時はproduction.ymlへ取得したドメインを記述する。)
default_url_options:
  host: 'localhost:3000'

Settings.hostで置き換える

  config.action_mailer.perform_caching = false
  config.action_mailer.default_url_options = { host: Settings.host }
  config.action_mailer.delivery_method = :letter_opener_web

ルーティングの追加

  resources :password_ressets, only: %i[new create edit update]

f:id:meo2:20210629163315p:plain

controller

  class PasswordResetsController < ApplicationController
  skip_before_action :require_login
   #パスワードリセット申請フォームのアクション
  def new; end
   #パスワードリセット申請フォームでemailを入力し、送信した時に実行。
  def create
    @user = User.find_by(email: params[:email])
   #form_withから送られたemailを受け取る。
    @user&.deliver_reset_password_instructions!
   #DBからデータを受け取っている場合、ユーザにトークン付きURLをメールで送信する。
    redirect_to login_path, success: 'パスワードリセット手順を送信しました'
  end
  #メールに送信されたtokenURLからパスワードリセットページへ遷移するアクション
  def edit
    @token = params[:id]
    #postされてきた値を取得
    @user = User.load_from_reset_password_token(@token)
    #リクエストで送信されてきたトークンを使って、ユーザーの検索を行う。
    #トークンが見つかり、有効であればそのユーザーオブジェクトを@userに格納。
    not_authenticated if @user.blank?
    #@userがnilまたは空の場合、not_authenticatedメソッドを実行する
  end
 #ユーザがパスワードリセットページで新しいパスワードを入力し、送信したときに実行。
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])
    return not_authenticated if @user.blank?

    @user.password_confirmation = params[:user][:password_confirmation]
    #password_confirmation属性の有効性を確認
    if @user.change_password(params[:user][:password])
    #change_passwordメソッドで、パスワードリセットに使用したトークンを削除し、パスワードを更新する 
       redirect_to login_path, success: 'パスワードを変更しました'
    else
       render 'edit'
    end
  end
end

パスワードリセット申請ページの作成

ユーザがパスワードをリセットしたいときにemailを入力し送信する画面。
f:id:meo2:20210630014249p:plain:w250:h100

view

  <% content_for(:title, t('.password')) %>
<h1>パスワードリセット申請</h1>
<%= form_with url: password_resets_path, local: true , method: :post do |form| %>
  <%= form.label :email, "メールアドレス" %><br>
  <%= form.text_field :email, class: 'form-control' %><br>
  <%= form.submit '送信', class: 'btn btn-primary' %>
<% end %>

パスワードリセット画面の作成

パスワードリセット画面はパスワード初期化メールに記載されたURLから行くページ。
f:id:meo2:20210630013941p:plain:w300:h200

view

  <% content_for(:title) { 'パスワードリセット' } %>
<h1>パスワードリセット</h1>
<%= form_with model: @user, url: password_reset_path(@token), local: true, method: :put do |f| %>
  <%= f.label :email %><br>
  <%= @user.email %><br>

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

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

  <%= f.submit '更新する', class: 'btn btn-primary' %>
<% end %>