MessageVerifier を使えばパスワード再発行メールの実装が簡単になる

More than 3 years have passed since last update.


MessageVerifier とは?

Rails 4.1 でアナウンスされた新機能。だと思ってる人が多いですが、4.1 でオモテに出てきたというだけで実は結構前から埋もれてたっぽい。(3.0.20 では使えた)

cookie_session の実装に使われているので、その辺りのコードを掘ったことのある人はご存知と思います。

さて、「パスワード再発行メール」のよくある実装はこんな。


  1. users テーブルに、 token と token_expire_at のカラムを用意

  2. 64文字くらいのランダム文字列を生成して token にセット

  3. token_expire_at に有効期限をセットしてレコード保存

  4. token を含むURLをユーザにメールで送信

  5. ユーザがURLを開くと token がサーバに渡ってくるので、 User.find_by(token: token) でユーザを引っ張ってくる

  6. 有効期限を確認し、期限内であれば処理続行

users テーブルにカラムを2つ足すのがちょっとね。

これを、 MessageVerifier を使うと以下のように出来ます。


  1. 64文字くらいの key を用意しておく。(システム全体で1つあればOK)


  2. "[123,'2014/10/12 12:34:56']" という文字列(123 は user_id、日時は期限) を key で暗号化し、 100文字弱の token を生成。どこにも保存しなくてOK

  3. token を含むURLをユーザにメールで送信

  4. ユーザがURLを開くと token がサーバに渡ってくるので、同じ key で復号

  5. user_id と期限が得られるので、期限内であれば処理続行

token カラム要らない!!!

注意点は、 key が漏洩すると token が偽造されてしまうということ。まああまり大きな問題ではないでしょう。


やり方

Rails 4.1 以降

class UserMailer < ActionMailer::Base

def password_reset(user)
token = Rails.application.message_verifier(:password_reset).generate([user.id, 1.day.since])
mail(to: user.email, body: edit_password_reset_url(token))
end
end

class PasswordResetController < ApplicationController
def edit
user_id, expire_at = Rails.application.message_verifier(:password_reset).verify(params[:token])
# ごにょごにょ
rescue ActiveSupport::MessageVerifier::InvalidSignature
# invalid token
end
end

それ以前

PASSWORD_RESET_KEY = 'hogefugahofefuga....'

class UserMailer < ActionMailer::Base
def password_reset(user)
verifier = ActiveSupport::MessageVerifier.new(PASSWORD_RESET_KEY)
token = verifier.generate([user.id, 1.day.since])
mail(to: user.email, body: edit_password_reset_url(token))
end
end

class PasswordResetController < ApplicationController
def edit
verifier = ActiveSupport::MessageVerifier.new(PASSWORD_RESET_KEY)
user_id, expire_at = verifier.verify(params[:token])
# ごにょごにょ
rescue ActiveSupport::MessageVerifier::InvalidSignature
# invalid token
end
end

どっちでも簡単ですね。

あー過去の実装全部コレで書き直したい。。。


ちょっと気になっていること

デフォルトの digest method が SHA1 なんだけど、 SHA1オワコン説を考慮したほうがいいのかどうか。

ActiveSupport::MessageVerifier.new(PASSWORD_RESET_KEY, 'SHA256')

で指定は出来るのだけど、何を指定すればいいのやら。