[Ruby on Rails]sorceryによる認証 – (4)パスワードリセット

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回に引き続きsorceryによる認証についてです。今回はパスワードリセットを実装してみました。

今回もsorceryで認証ができるアプリに機能を追加する形で実装していきます。またパスワードリセット時にメールを送信するため、ActionMailerの設定も必要です。予め以下の記事を参考にemail・passwordでアカウントを登録し、ActionMailerでメールを送信できるアプリを作成しておいてください。
公式チュートリアル(Simple-Password-Authentication)
公式チュートリアル(User-Activation)
以前の記事

パスワードリセットの画面フロー

今回作成する機能のイメージを掴みやすくするため、画面のスクリーンショットを遷移順に貼っておきます。

1.ログイン画面

sorcery_password_reset_1_first

ログイン画面の下部にパスワードを忘れた場合にメールアドレスを入力する欄があります。ここにメールアドレスを入力して「Reset my password!」を押します。

2.メール送信

画面

sorcery_password_reset_2_success_message

メール

sorcery_password_reset_3_mail

画面にはメールを送信した旨のメッセージが表示されます。送られてくるメールにはパスワードリセット画面のURLが書かれています。

3.パスワードリセット画面

sorcery_password_reset_4_choose_password

メールに書かれたURLにアクセスすると、新しいパスワードを設定する画面が表示されます。パスワードを入力し「Update User」ボタンを押します。

4.パスワードリセット完了

sorcery_password_reset_5_password_update

パスワードがリセットされ、完了した旨のメッセージが表示されます。

アプリケーションの実装

では実装についてです。今回も公式チュートリアルをなぞりましたが、幾つか追加したり変更した手順もありますので合わせて紹介したいと思います。

1.「reset_password」サブモジュールのインストール

パスワードリセットを行うためのサブモジュールをインストールします。以下のコマンドを実行してください。

$ rails g sorcery:install reset_password --migrations

以下のようなマイグレーションファイルが作成されます。

class SorceryResetPassword < ActiveRecord::Migration
  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
  end
end

マイグレーションを実行してDBに反映します。

$ rake db:migrate

またsorceryの定義ファイルに、使用するサブモジュールとして「reset_password」が追加されていることを確認してください(無ければ追加してください)。

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

2.ActionMailerの定義

ActionMailerの定義を行います。まず以下のコマンドを実行してください。

$ rails g mailer UserMailer reset_password_email

前回の続きから行っている場合など既に「UserMailer」が作成済みの場合は、上書きするかを選択するメッセージが表示されるので注意してください。今回は実装済みの機能を壊したくなかったなめ、上書きしないようにしました。

sorceryの定義ファイルにパスワードリセットに使用するActionMailerとしてUserMailerを定義します。

config/initializers/sorcery.rb
config.user_config do |user|
  (中略)
  user.reset_password_mailer = UserMailer
  (中略)
end

パスワードリセット時のメール本文を定義します。

app/views/user_mailer/reset_password_email.text.erb
Hello, <%= @user.email %>
===============================================

You have requested to reset your password.

To choose a new password, just follow this link: <%= @url %>.

Have a great day!

テキスト形式のメールのみ定義したため「app/views/user_mailer」配下の「〜.html.erb」は削除します。

アクションを実装します。

app/mailers/user_mailer.rb
  def reset_password_email(user)
    @user = User.find user.id
    @url = "http://0.0.0.0:3000" + edit_password_reset_path(@user.reset_password_token)
    mail(:to => user.email,  :subject => "Your password has been reset")
  end

チュートリアルではURLの取得に「edit_password_reset_url」を使用していましたが、Rails4では以下のエラーとなってしまいました。

Missing host to link to!

このため使用するメソッドを「edit_password_reset_path」に変更しています。またメールに「http」を含むURLの形式で出力するよう「"http://0.0.0.0:3000"」と連結しています。
これはsorceryの履歴の記述を参考にしました。

When use Rails4, an error occurs "Missing host to link to! Please provide the :host parameter". so I change 'edit_password_reset_url' to 'edit_password_reset_path'

sorceryの履歴より

3.パスワードリセットを行うコントローラを定義

パスワードリセットを行うコントローラを作成します。

$ rails g controller PasswordResets create edit update

作成されたコントローラを以下のように編集します。

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

  def create
    @user = User.find_by_email(params[:email])
    @user.deliver_reset_password_instructions! if @user
    redirect_to(root_path, :notice => 'Instructions have been sent to your email.')
  end

  def edit
    set_token_user_from_params?
  end

  def update
    return if !set_token_user_from_params?

    @user.password_confirmation = params[:user][:password_confirmation]

    if @user.change_password!(params[:user][:password])
      redirect_to(root_path, :notice => 'Password was successfully updated.')
    else
      render :action => "edit"
    end
  end

  private

    def set_token_user_from_params?
      @token = params[:id]
      @user = User.load_from_reset_password_token(params[:id])

      if @user.blank?
        not_authenticated
        return false
      else
        return true
      end
    end
end

チュートリアルでは「edit」「update」のそれぞれで

  • Postされてくる値とユーザオブジェクトを取得する
  • ユーザオブジェクトの存在チェックを行う


を行っていますが、今回はこれらの処理を「set_token_user_from_params?」に纏めています。

作成したコントローラをルーティングに追加します。

config/routes.rb
resources :password_resets

同時に以下をルーティングから削除します。

  get 'password_resets/create'
  get 'password_resets/edit'
  get 'password_resets/update'

4.画面の作成

パスワードをリセットするためのフォームを作成します。

app/views/password_resets/edit.html.erb
<h1>Choose a new password</h1>
<%= form_for @user, :url => password_reset_path(@token), :html => {:method => :put} do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2 id="toc-prohibited-this-user-from-being-saved"><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :email %><br />
    <%= @user.email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

パスワードを忘れた場合にメールアドレスを入力するフォームを、ログイン画面に追加します。

app/views/user_sessions/new.html.erb
(中略)
<h1>Forgot Password?</h1>
<%= render 'forgot_password_form' %>

テンプレート(forgot_password_form)を呼び出しているので、このテンプレートを定義します。

app/views/user_sessions/_forgot_password_form.html.erb
<%= form_tag password_resets_path, :method => :post do %>
  <div class="field">
    <%= label_tag :email %><br />
    <%= text_field_tag :email %> <%= submit_tag "Reset my password!" %>
  </div>
<% end %>

まとめ

以上でパスワードリセット機能を実装することができました。前回と同じく、非常の簡単に実装できました。

参考サイト

以下のサイトを参考にさせて頂きました。ありがとうございました。
公式チュートリアル(Reset password)
sorceryの履歴