##パスワードリセット機能の実装
パスワードを忘れてしまった時に、メールアドレスを入力しパスワードを再設定する、これをrails
での導入を行う。
その際に主に利用するツールは、sorceryのモジュールであるreset_password
、letter_opener_webというgem
、configというgem
、ActionMailer
を使って実装する。
◆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
と入力。
すると、下記のマイグレーションファイルが作成される。
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モデル
にバリデーションを行う。
#以下の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.rb
にreset_password
サブモジュールを追加し、パスワードリセットに使用するActionMailer
としてUserMailer
を定義。
#デフォルトでは[]内が恐らく空になっているので、: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
と入力。
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(新パスワード作成画面)**だけ作成。
<% 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>
<% 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つのファイルに記述した方が多方面性的に優れるから。
<p><%= @user.decorate.full_name %>様</p>
<p>===============================================</p>
<p>パスワード再発行のご依頼を受け付けました。</p>
<p>こちらのリンクからパスワードの再発行を行ってください。</p>
<p><a href="<%= @url %>"><%= @url %></a></p>
<%= @user.decorate.full_name %>様
===============================================
パスワード再発行のご依頼を受け付けました。
こちらのリンクからパスワードの再発行を行ってください。
<%= @url %>
・設定
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
を入れる。
・導入
Gemfileにgem 'letter_opener_web'
を記入して、bundle install
を実施。
・設定
letter_opener_web
では、ブラウザ上でメールを確認するため、
ルーティングにLetterOpenerWeb
にアクセスするために必要な記述を追記。
#以下の1文を追記
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
config/environments/development.rb
にて以下の2文を追記。
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情報
を切り分ける。
・導入
Gemfileにgem '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
に設定を記述する。
default_url_options:
host: 'localhost:3000'
##参考
Sorcery/sorcery
⭐️パスワードリセット機能の実装
【Rails】パスワード変更(トークンがどのように使用されているのか)
gem configを理解する ~ configを使った定数管理の方法
gem letter_opener を試してみる