新規にユーザーを登録したとき、そのアドレス宛にアカウントを有効化するためのURL付のメールを送信し、そこにアクセスすることでアカウントを有効化する。
AccountActivationsリソース
account_activationsコントローラーの準備
$ rails generate controller AccountActivations
ルートの設定
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
end
usersテーブルへカラムを追加。
$ rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
それぞれの役割は、
activation_digest 有効化のためのトークン
activated 有効化済かどうか
ativated_at 有効化された日時
class AddActivationToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :activation_digest, :string
add_column :users, :activated, :boolean, default: false
add_column :users, :activated_at, :datetime
end
end
activatedのdefault値をfalseに指定。
$ rails db:migrate
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
.
.
.
private
# メールアドレスをすべて小文字にする
def downcase_email
self.email = email.downcase
end
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
ここで重要なのはcreate_activation_digestメソッド。
このメソッドはbefore_createコールバックによって、createアクションが実行される前に実行される。
このメソッでの処理は、
①activation_token(attribute_accessorで定義した仮の属性)にランダムなトークンを代入。
②activation_digestカラムにトークンをダイジェスト化して代入。
このメソッドでは代入だけで保存する処理がされていないが、コールバックによってcreateアクションの前に実行されるため、アクション内で保存がされる。
そのため、この時点で新しくuserを作れば、activation_digestカラムに値の入ったuserが生成されうようになる。
ちなみに、トークンの生成メソッドとダイジェスト化のメソッドはこれ
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def User.new_token
SecureRandom.urlsafe_base64
end
cookieを使った永続ログインやパスワードの再設定にもこのメソッドを使う。
アカウント有効化のメール送信
次にメーラーの設定。
$ rails generate mailer UserMailer account_activation password_reset
mailers/user_mailer.rbの中にアカウント有効化のためのメソッドとパスワード再設定のためのメソッドが自動で生成される。
しかしそのままでは使えないため編集する。
mailers/application_mailer.rbも生成されているが、こちらは別に触らなくても問題ない。
チュートリアル上では送信元のアドレスを変えているだけなので、そのままでも動作する。
class UserMailer < ApplicationMailer
def account_activation(user)
@user = user
mail to: user.email, subject: "Account activation"
end
#パスワード再設定用
def password_reset
@greeting = "Hi"
mail to: "to@example.org"
end
end
引数でuserを特定し、そのアドレスへメールを送信する。
Hi <%= @user.name %>,
Welcome to the Sample App! Click on the link below to activate your account:
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
<h1>Sample App</h1>
<p>Hi <%= @user.name %>,</p>
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<%= link_to "Activate", edit_account_activation_url(@user.activation_token,
email: @user.email) %>
上二つはメールのテンプレート。
text.erbとなっている方がテキストベース。
ここでアカウントを有効化するためのURLを生成している。
この部分。
edit_account_activation_url(@user.activation_token, email: @user.email)
例えば、editページへ変移したいときにはedit_user_path(user)と書き、これで/users/:id/editといったURLが生成される。
ここでは、@user.activation_tokenを:idとみなしてURLを生成し、その後にuserのアドレスをくっつけている。つまり、ここでは以下のようなURLを生成する。
/users/トークン/edit/メールアドレス
ここのトークンはparams[:id]で、メールアドレスはparams[:email]でそれぞれ取得できる。
次に、このメールのプレビューを確認する。
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/account_activation
def account_activation
user = User.first
user.activation_token = User.new_token
UserMailer.account_activation(user)
end
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/password_reset
def password_reset
UserMailer.password_reset
end
end
ここは特に説明の必要はなさそうなのでスキップ。
メソッドの上のURLにアクセスすればプレビューが表示される。開発環境によってlocal:3000(host名)を書きかえる。
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
新しいuserを生成するためのアクション。
ここで重要なのは、
UserMailer.account_activation(@user).deliver_now
の一行のみ。
user_mailer.rbで記述したaccount_activation(user)メソッドを呼び出し、引数に@userが入っている。この@userのメールアドレスに向けてメールを送信する、という処理になっている。
これでuserの新規作成は完了。
後はactivatedをdefaultのfalseからtrueに変えてやる処理だけ。
アカウントを有効化する
アカウントを有効化するためには、activation_digestの値とactivation_tokenの値が一致するかを確認する。
ここで、ログイン永続化のため、remember_digestとremember_tokenが一致するか調べるために用いたauthenticater?メソッドを使うが、今のままでは使用できない。
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
このメソッドはremembet_tokenのためのメソッドなので、このままではactivation_tokenに流用することができない。
これを今回や、パスワード再設定の際にも使用できるように、下のように書き換える。
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
このメソッドは user.authenticated?(:activation, params[:id])という形で実際にeditアクションの中に組み込む。
引数の:activationはauthenticated?メソッド内でattributeに代入され、activationd_digestとして振る舞うようになる。
第二引数のparams[:id]は、有効化のためのURLに含まれるトークンのこと。
BCrypt::Password.new(digest).is_password?(token)
ここでダイジェストとトークンの比較を行っている。
authenticated?メソッド内で使われているsendメソッドが重要な役割を果たしているので、以下のコンソールでの動作を確認しておく。
>> user = User.first
>> user.activation_digest
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send(:activation_digest)
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send("activation_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> attribute = :activation
>> user.send("#{attribute}_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated, true)
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
editアクション。
①URLに含まれているメールアドレスからuserを特定。
②userが存在し、かつ、user.activatedがfalseであり、かつ、activation_digestとURLに含まれているactivation_tokenが一致されば、処理を実行。
③activatedをtrueに更新。
④activated_atを現在時刻(更新された時刻)に更新
⑤ログイン
⑥users/showへ
といった流れ。
最後に、有効化が済んでいないuserのログインを拒否する処理を追加する。
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link."
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
if user.activated?
この条件分岐を追加しただけ。trueの時だけログインできる。