3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】パスワードリセット機能を実装する

Posted at

実装の流れ

  1. sorceryのReset passwordモジュールをインストールする
  2. Mailerの作成
  3. コントローラの作成
  4. トークンにユニーク制約を付与
  5. ビューファイルの作成
  6. letter_opener_webの導入
  7. configを導入

公式wikiに手順が説明されている。
https://github.com/Sorcery/sorcery/wiki/Reset-password

sorceryのサブモジュールをインストール

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

以下のファイルが生成される。
config/initializers/sorcery.rb
app/models/user.rb
db/migrate/20231105121233_sorcery_reset_password.rb

しかし私の場合、app/models/user.rbでエラーのような文が表示された。

gsub  config/initializers/sorcery.rb
File unchanged! The supplied flag value not found!  app/models/user.rb
create  db/migrate/20240227224617_sorcery_reset_password.rb

とりあえず進める。

生成されたファイルを確認する

config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:reset_password]
db/migrate/20240227224617_sorcery_reset_password.rb
class SorceryResetPassword < ActiveRecord::Migration[7.0]
  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
カラム 説明
reset_password_email_sent_at パスワードリセット用のメールを送信した時刻を保持するためのカラム
reset_password_token_expires_at トークンの有効期限が切れる時刻を記録するカラム
reset_password_token トークン文字列を保持するカラム
access_count_to_reset_password_page パスワードリセット画面にアクセスした回数を記録するために用意されたカラムです。N回以上はパスワードリセット画面に移動できないようにするなどの機能を実装する時に用いられるカラム

マイグレーションする

$ rails db:migrate

パスワードリセット用のMailerを作成する

rails g mailer UserMailer reset_password_email

生成されたファイルを確認

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

app/mailers/user_mailer.rbが作成されている。
app/views/user_mailerの下に二つのビューファイルが作成されている。
html形式のメールとtext形式のメールが用意されているということ。

メールを送信するメソッドを作成

app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  default from: 'from@example.com'

  def reset_password_email(user)
    @user = User.find(user.id)
    @url  = edit_password_reset_url(@user.reset_password_token)
    mail(to: user.email,
         subject: t('defaults.password_reset'))
  end
end

@user@urlはmailのviewファイルで使用する。

パスワードリセットに使用するmailerを定義する

config/initializers/sorcery.rb にreset_passwordサブモジュールを追加し、パスワードリセットに使用するActionMailerとしてUserMailerを定義する記述をする。

config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:reset_password]

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.reset_password_mailer = UserMailer 
  end 
end

既にファイル内に記述がコメントアウトしてあったので、必要分のみ追記しました。

user.reset_password_mailer = の部分にUserMailerを付け加え

Mailerのビューファイルの設定

app/views/user_mailer/reset_password_email.text.erb
<%= @user.decorate.full_name %>
===============================================
 
パスワード再発行のご依頼を受け付けました

こちらのリンクからパスワードの再発行を行ってください

<%= @url %>
app/views/user_mailer/reset_password_email.html.erb
<p><%= user.decorate.full_name %></p>

<p>パスワード再発行のご依頼を受け付けました</p>

<p>こちらのリンクからパスワードの再発行を行ってください</p>

<p><a href="<%= @url %>"><%= @url %></a></p>

@url の中身は、tokenで発行された再登録用のurlのこと。

コントローラの作成

rails g controller PasswordResets create edit update
create  app/controllers/password_resets_controller.rb
invoke  erb
create    app/views/password_resets
create    app/views/password_resets/create.html.erb
create    app/views/password_resets/edit.html.erb
create    app/views/password_resets/update.html.erb
invoke  decorator
create    app/decorators/password_reset_decorator.rb

コントローラの中身を記述

app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
  skip_before_action :require_login

# パスワードリセット申請画面を表示するアクション
  def new; end

# パスワードのリセットをリクエストするアクション。
# ユーザーがパスワードのリセットフォームにメールアドレスを入力して送信すると、このアクションが実行される。
  def create
    @user = User.find_by(email: params[:email])
    @user&.deliver_reset_password_instructions!
    # 「存在しないメールアドレスです」という旨の文言を表示すると、逆に存在するメールアドレスを特定されてしまうため、
    # あえて成功時のメッセージを送信させている
    redirect_to login_path, success: t('.success')
  end

# # パスワードのリセットフォーム画面へ遷移するアクション
  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(@token)
    not_authenticated if @user.blank?
  end

 # ユーザーがパスワードのリセットフォームを送信したときに発生するアクション
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(@token)

    return not_authenticated if @user.blank?

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password(params[:user][:password])
      redirect_to login_path, success: t('.success')
    else
      flash.now[:danger] = t('.fail')
      render :edit, status: :unprocessable_entity
    end
  end
end

ルーティングの設定

config/routes.rb
resources :password_resets, only: [:new, :create, :edit, :update]

usersテーブルのtokenにunique制約とuniqueバリデーションを付与

tokenは一意なものでなければならないのでユニーク制約を付ける。

app/models/user.rb
validates :reset_password_token, uniqueness: true, allow_nil: true

ビューファイルを編集

ログインページにパスワードリセットページへのリンクを追加

<%= link_to t('user_sessions.new.to_register_page'), new_user_path %>
<%= link_to t('user_sessions.new.password_forget'), new_password_reset_path %> #追記

パスワードリセット申請画面を作成

app/views/password_resets/new.html.erb
<% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 col-lg-8 mx-auto">
      <h1><%= t('.title') %></h1>
      <%= form_with url: password_resets_path do |f| %>
        <div class="mb-3">
          <%= f.label :email, class: "form-label" %>
          <%= f.email_field :email, class: 'form-control' %>
        </div>
        <%= f.submit t('.submit'), class: "btn btn-primary" %>
      <% end %>
    </div>
  </div>
</div>

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

app/views/password_resets/edit.html.erb
<% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 col-lg-8 mx-auto">
      <h1><%= t('.title') %></h1>
      
      <%= form_with model: @user, url: password_reset_path(@token), method: :patch do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <div class="mb-3">
      <%= f.label :email, class: "form-label" %>
      <%= @user.email %>
      </div>

      <div class="mb-3">
      <%= f.label :password, class: "form-label" %>
      <%= f.password_field :password, class: "form-control" %>
      </div>

      <div class="mb-3">
      <%= f.label :password_confirmation, class: "form-label" %>
      <%= f.password_field :password_confirmation, class: "form-control" %>
      </div>
      <%= f.submit class: "btn btn-primary" %>
      <% end %>
    </div>
  </div>
</div>

letter_opener_webを追加する

letter_opener_weを使うと、開発環境で実際にメールを送信せずに、メールの送信を確認できる。
https://github.com/fgrehm/letter_opener_web

開発環境でしか使わないため、development配下に記載する。

Gemfile
group :development do
  gem 'letter_opener_web', '~> 2.0'
end

bundle installする

bundle install

ルーティングの設定をする

config/routes.rb
Rails.application.routes.draw do
  mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end
config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener 
config.action_mailer.default_url_options = { host: 'localhost:3000' } 

メールが届くかどうかは以下の画面から確認する

configを導入する

gem configを利用して、環境ごとの定数を便利に管理できるようになる。

Gemfile
gem 'config'
bundle install

以下のコマンドを入力してファイルを生成

rails g config:install

ファイルが生成される

create  config/initializers/config.rb
      create  config/settings.yml
      create  config/settings.local.yml
      create  config/settings
      create  config/settings/development.yml
      create  config/settings/production.yml
      create  config/settings/test.yml
      append  .gitignore

生成された設定ファイルの役割

ファイル名 役割
config/initializers/config.rb configの設定ファイル
config/settings.yml すべての環境で利用する定数を定義
config/settings.local.yml ローカル環境のみで利用する定数を定義
config/settings/development.yml 開発環境のみで利用する定数を定義
config/settings/production.yml 本番環境のみで利用する定数を定義
config/settings/test.yml テスト環境のみで利用する定数を定義

.gitignoreには以下の内容が自動で追記される

.gitignore
config/settings.local.yml
config/settings/*.local.yml
config/environments/*.local.yml

開発環境での定数を設定

config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener_web 
config.action_mailer.default_url_options = Settings.default_url_options.to_h
config/environments/test.rb
config.action_mailer.default_url_options = Settings.default_url_options.to_h

config.action_mailer.delivery_method = :letter_opener_web
→Railsアプリケーションでメールを送信する際に使用する配送方法(delivery method)を設定している。

config.action_mailer.default_url_options = Settings.default_url_options.to_h
→ActionMailerがメールを生成する際に使用するデフォルトのURLオプションを設定している。
以下のdevelopment.ymlで記述された定数default_url_optionsを読み込んでいる。

host情報をsettings/development.ymlに記載する

ホスト情報は開発環境とテスト環境の両方に設定する

config/settings/development.yml
default_url_options:
  host: 'localhost:3000'
config/settings/test.yml
default_url_options:
  host: 'localhost:3000'
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?