LoginSignup
11
6

More than 1 year has passed since last update.

【Rails】メール送信でパスワードリセット機能の実装

Posted at

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

パスワードを忘れてしまった時に、メールアドレスを入力しパスワードを再設定する、これをrailsでの導入を行う。

その際に主に利用するツールは、sorceryのモジュールであるreset_passwordletter_opener_webというgemconfigというgemActionMailerを使って実装する。

◆reset_password
ユーザー認証用に導入したgem sorceryには、単純なパスワードによる認証機能に加え様々なモジュールが用意されている。その中の1つがreset_passwordである。
 ▷他のモジュールの使い方はここを参考

◆letter_opener_web
letter_opener_webは実際のアドレスにパスワードリセットメールを送ると、毎回確認が面倒なので、メールを送信する代わりに、デフォルトのブラウザでプレビューしてくれるものになる。

開発環境で電子メール配信を設定する必要がなく、誤って他の人のアドレスにテスト電子メールを送信することを心配する必要が無いため非常に便利。
(似たようなgemでletter_openerがあるが、こちらはリモートで開発していると使えないことがあるらしいので基本的にはletter_opener_web

◆config
configとは環境ごとに定数を便利に管理できるGem。
アプリを開発していく中で、本番環境と開発環境で値を変えたいといったケースに使われる。定数を管理する箇所が1箇所にまとまるので保守性も非常に高まるものとなっている。

◆ActionMailer
Railsには、デフォルトでActionMailerと呼ばれるメール送信機能が備わっている。
通常のテキストメール送信に加え、HTMLメールが送信できたり、添付ファイルを付けるなど様々なオプションがある。
 ▷Action Mailer の基礎

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

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

①パスワードリセット申請時に、ユニークなトークンを発行し、DBに保存

②メールアドレスに、パスワードリセットページへのリンクを送信。発行されたトークンをURLに組み込むことにより、どのユーザーからの申請かを判別。

③届いたメールに記載されたURLから、パスワードリセットページへと移動し、新しいパスワードを作成。

手順

1.モジュール reset_passwordの導入
2.Mailerの導入
3.reset_password ルーティング・コントローラーの作成
4.reset_password のview
5.メール内容の作成・設定
6.gem letter_opener_webの導入・設定
7.gem configの導入・設定

1.モジュール reset_passwordの導入

ターミナルにてrails g sorcery:install reset_password --only-submodulesと入力。

すると、下記のマイグレーションファイルが作成される。

sorcery_reset_password.rb
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を実施。
ファイル内を見るとtokenが新たにカラムとして追加されているので、userモデルにバリデーションを行う。

user.rb
#以下の1行をuserモデルに追加
validates :reset_password_token, uniqueness: true, allow_nil: true

uniqueness: true
tokenは唯一無二でなけれならない。そうでないと、別の人のパスワードを変更できてしまう可能性があるため。

allow_nil: true
allow_nil: trueとは対象の値がnilの場合にバリデーションをスキップするもの。
このオプションが付いていない場合は、nil値も重複とみなされバリデーションエラーが発生する。
→ユーザーを複数登録できなくなる問題が発生する。(トークンの初期値はnilであるため)

2.Mailerの導入

ターミナルにてrails g mailer UserMailer reset_password_emailと入力。

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

config/initializers/sorcery.rb
#デフォルトでは[]内が恐らく空になっているので、:reset_passwordを記入
Rails.application.config.sorcery.submodules = [:reset_password]

#デフォルトではコメントアウトされているので、探してUserMailerと記入
user.reset_password_mailer = UserMailer

3.reset_password ルーティング・コントローラーの作成

・ルーティング
routes.rbにてresources :password_resets, only: %i[new create edit update]の一文を追記。

・コントローラー
ターミナルにてrails g controller PasswordResets new create edit updateと入力。

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: "メールを送信しました。"
  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: "パスワードを変更できました"
    else
      flash.now[:danger] = "パスワード変更出来ませんでした"
      render :edit
    end
  end
end

deliver_reset_password_instructions!
 → 有効期限付きのリセットコードを生成し、ユーザーにメールを送信するという意味

User.load_from_reset_password_token
 → tokenが見つかり、有効であればユーザーを返すという意味

change_password
 → tokenをクリアして、ユーザーの新しいパスワードを更新しようとする意味

4.reset_password のview

リセットパスワードのnew,create,edit,updateの役割は以下の通り。

new
ユーザーがパスワードリセットをしたい時に、申請するための画面を表示。

create
パスワードリセット対象のメールアドレスを申請して、リセットの対象を作成。

edit
メールに届いた、パスワードリセット用のリンクに飛んで、新しいパスワードを入力。

update
パスワードリセット(edit)ページにて、『更新する』ボタンでパスワードを変更。

viewはnew(パスワードリセット申請画面)edit(新パスワード作成画面)だけ作成。

(パスワードリセット申請画面)new.html.rb
<% content_for(:title ,'パスワードリセット申請')%>
<div class="container">
    <div class="row">
        <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
            <h1>パスワードリセット申請</h1>
            <%= form_with url:password_resets_path,method: :post,local: true do |f|%>
                <div class="form-group">
                    <%=f.label :email, User.human_attribute_name(:email)%>
                    <%=f.text_field :email,class: "form-control"%>
                </div>
                <%=f.submit "送信",class:"btn btn-primary"%>
            <%end%>
        </div>
    </div>
</div>
(新パスワード作成画面)edit.html.rb
<% content_for(:title,"パスワードリセット")%>
<div class="container">
    <div class="row">
        <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
            <h1>パスワードリセット</h1>
            <%= form_with model:@user,url:password_reset_path(@token),local: true do |f|%>
                <%=render "shared/error_messages",object: f.object%>
                <div class="form-group">
                    <%=f.label :email%>
                    <%=@user.email%>
                </div>

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

                <div class="form-group">
                    <%=f.label :password_confirmation%>
                    <%=f.password_field :password_confirmation,class: "form-control"%>
                </div>
                <%=f.submit "更新する",class:"btn btn-primary"%>
            <%end%>
        </div>
    </div>
</div>

5.メール内容の作成・設定

・メール内容の作成
メール文の作成は、app/views/user_mailer配下にあるreset_password_email.text.erb、とreset_password_email.html.erbに記載する。

Mailerはメール送信の呼び出しが行われた場合、
app/mailer配下のUserMailerクラス内の、password_resetというメソッドが呼び出され、そこで送信先やメールのタイトルを指定後、
password_reset.text.erbファイルに記載された内容が送信される。

そこでhtmlファイルにも記述する理由としては、htmlファイルで装飾されたメールの方が見やすかったりする訳である。
逆に顧客によってhtmlメールを嫌がる(容量が大きい、ガラケーで表示できない等)方もいるので2つのファイルに記述した方が多方面性的に優れるから。

reset_password_email.html.rb
<p><%= @user.decorate.full_name %>様</p>
<p>===============================================</p>

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

<p>こちらのリンクからパスワードの再発行を行ってください。</p>
<p><a href="<%= @url %>"><%= @url %></a></p>
reset_password_email.text.rb
<%= @user.decorate.full_name %>様
===============================================

パスワード再発行のご依頼を受け付けました。

こちらのリンクからパスワードの再発行を行ってください。
<%= @url %>

・設定
app/mailers/user_mailer.rbにて、先ほど作成したパスワードリセットするためのメール本文を送信するためのメソッドを定義する。

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: "パスワードリセット")
  end

end

default from: 'メールの送信元'
→ メールの送信元のアドレスを指定できる。

mail(to: 宛先,subject: '件名')
→ メールの宛先、件名を指定

6.gem letter_opener_webの導入・設定

開発環境では実際のメールは送られないように設定するために、letter_opener_webを入れる。

・導入
Gemfilegem 'letter_opener_web'を記入して、bundle installを実施。

・設定
letter_opener_webでは、ブラウザ上でメールを確認するため、
ルーティングにLetterOpenerWebにアクセスするために必要な記述を追記。

routes.rb
#以下の1文を追記
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?

config/environments/development.rbにて以下の2文を追記。

config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true

config.action_mailer.delivery_method
→ 配信方法を指定。

config.action_mailer.perform_deliveries
→ メールを実際に配信するかどうかを指定。デフォルトはtrueとなっている。

action_mailerのさまざまな設定に関しては【ここ】に詳細が記載。

メールが送られたか確認する場合にはここで確認 ⏩ http://localhost:3000/letter_opener

7.gem configの導入・設定

環境ごとに違う定数を使いたいため、このconfigを用いてhost情報を切り分ける。

・導入
Gemfilegem 'config'を記入して、bundle installを実施。

・設定
ターミナルにてrails g config:installを入力。
すると環境ごとのファイルが生成される。

*全てconfigファイル配下

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

今回は開発環境のみで行うのでconfig/settings/development.ymlに設定を記述する。

config/settings/development.yml
default_url_options:
  host: 'localhost:3000'

参考

Sorcery/sorcery
⭐️パスワードリセット機能の実装
【Rails】パスワード変更(トークンがどのように使用されているのか)
gem configを理解する ~ configを使った定数管理の方法
gem letter_opener を試してみる

11
6
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
11
6