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でメールが送られるか確認。
環境変数の設定
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]
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を入力し送信する画面。
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から行くページ。
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 %>