メールアドレスを使った認証の方法
8,9章の認証は
----.authenticate?(=====)
とするなら
sessionを使った認証では、----の部分がDBにあるpassword_digestであり、===の部分はUserに入力してもらったpasswordだった
cookiesを使った認証では、---はDBにあるremember_digestであり、===はcookiesに保存されているremember_token
今回の場合、
---の部分は
まず、DBに認証用のdigestを保存するために、開発側で平文を用意し、ハッシュ値をかけてDBに保存する
ユーザーに送ったメールにはactivation_tokenとユーザーのemail情報を含めている
ユーザーがlinkをクリックすると、GETリクエストが送られたとき、userのemail情報がparamsに格納されているので、それを使って、User情報を取得する、そのUser情報からDBのactivation_digestを取得する
そして、===にはactivation_tokenがそのまま入る、ここはsessionやcookiesと変わらない
そのactivation_digestとactivation_tokenを認証する
signup (必要事項の記入) =
before_action (tokenの発行、ハッシュ化してactivation_digestとしてDBに保存) ->
create action (User情報を格納、Userに認証用メールを送信,emailとactivation_tokenが送信) =
User.mailer (account_activationメソッドが呼び出される、controllerに相当) ->
app/views/user_mailer/account_activation.text.erb(メール文面,viewに相当) ->
Userがリンクをクリック = GETリクエストが送られる ->
ルーティングが作動 ->
accout_activation controllerのedit action (実際の認証)
リクエストが送られた時のルーティングとコントローラを準備する必要がある
ルーティング
GET /accout_activations/:id/edit
:id = token
コントローラー
find_byでparams[:emailを取得
authenticated?で認証
params[:id]でtokenを受け取る
attr_accessor activation_token
一時的に保存し、忘れる前にメールで送る
メソッド参照
メソッドをあとで作る
before_create signupの時にだけ反応するコールバック処理
before_save 保存する前に必ず行うコールバック処理
このコールバック処理をメソッド参照で行うことができる
#before_createなので、signupする直前に実行
def create_activation_digest
self.activation_token = User.new_token
#ランダムなトークンを発行し、attr_accessorで作った仮想的な属性に入れておく
self.activation_digest = User.digest(activation_token)
#そのtokenをハッシュ化させたものをDBのdigestに保存
#正確にはdigestに代入されたあと、createで反映されるのでDBが保存される
end
メールを送信 - GETリクエストを送る
def account_activation
@greeting = "Hi"
mail to: "to@example.org" # => return mail object
#中身はapp/views/user_mailer/account_activation.text.erbと
#app/views/user_mailer/account_activation.html.erbとなり
#account_ativationを呼び出した何かがmail objectを実行する ???
end
textとhtml.erbはviewに似ている
Mailer = Controller
Mailの文面 = View
メールのURLには平文とともにメールアドレスも入れないと誰の平文か分からない
リンクを作る(メールの文面)時に
edit_user_url(user)
が
http://www.example.com/users/1/edit
になるらしいが、どこでやった?
その考え方を使い、
http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit
としたい
さらに、メールアドレスも含めた文面にしたいので、
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
としたいので、
引数にemailも加えて、最終的に
edit_account_activation_url(@user.activation_token, email: @user.email)
となる
コントローラーの実装
create controller -
User登録をする時に、アカウント認証を行いたいのでcreateアクションに組み込む
今まではsignup(userに必要事項を記入)したら、すぐにloginできる仕様だった
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to user_path(@user)
else
render "new"
end
end
今回は、これを修正して
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
#user_mailer.rbのaccout_activationが呼び出されている
#deliver_nowは返ってきたメールオブジェクトを実際に送信する
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
edit actionの実装
@user = User.find_by(email: params[:email]) # メールからUser情報を取得
@user.authenticated?(params[:id])
#DBのactivation_digestとメールから取得したactivation_tokenを照合する
しかし、authenticatedメソッドはrememberで固定してしまっているので、DRYの観点からauthenticated?メソッドを抽象化しなければ使えない
元々のauthenticated?メソッドは
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
だったが、以下のように変更
def authenticated?(attribute, token)
digest = self.send("#{attribute}_digest")
#sendを使えば動的にメソッドを変更できる、式展開をより実際的に使うことができる
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
これでeditアクションでauthenticated?を使うことができる
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
#userはnilチェック
#!user.activated?は何回もクリックされないために、activatedがfalseである時だけ実行
#authenticated?は認証
user.update_attribute(:activated, true) #activatedを有効に
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end