2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails tutorial 11章 メール有効化

Last updated at Posted at 2019-04-04

はじめに

Railsチュートリアルを自分が理解しやすい表現に置き換える目的で利用しています。 手順を示す記事ではありません

目的

新規登録のユーザーに確認用メールを送り、有効化する

AccountActivationsソリース

注意
アカウントの有効化ステータスを変更するには本来PATCHリクエストのupdateアクションになるが、今回はMailerを使うので、有効化リンクをメールで送信することになる
つまり、ユーザーがリンクをクリックしブラウザに送られるアクションGetリクエストになるので、eidtアクションを使う

有効化トークン

有効化トークンには、記憶トークンのような仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するやり方をとる

user.activation_token

仮想属性の有効化トークンにアクセス

user.authenticated?(:activation, token)

ユーザーを認証する

ユーザーの有効をテストするために、activated属性を追加し論理値を取るようにする

if user.activated?

データモデルの追加

rails generate migration add_activation_to_users \
>activation_digest:string activated:boolean activated_at:datetime

生成されたマイグレーションがこちら

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属性のデフォルト値をfalseにしています(default: false)

before_create

6章でメールアドレスの作成及び更新の際にコールバックが呼ばれるようUserモデルにbefore_saveを定義しましたが、今回の場合では作成されたときだけ呼び出したいのでbefore_createを定義する

user.rb
before_create :create_activation_digest
.
.
private
  def create_activation_digest
  end

create_activation_digestはUserモデル内でしか使わない為、privateで隠蔽しておきます

create_activation_digestメソッドでは、トークンに対応するダイジェストを割り当てる

user.rb
attr_accessor :remember_token, :activation_token
.
.
private
  def create_activation_digest
    salf.activation_token = User.new_token
    self.activation_digest = User.digest(activation_token)
  end

attr_accessorに追加したのは、:remember_token同様、:activation_tokenも仮のものでなくてはならないから(攻撃を回避するために必要)

UserMailer

それでは有効化のメール送信に必要なActionMailerライブラリを使ってUserのメイラーを追加します

rails generate mailer UserMailer account_activation password_reset

これでtext用のビューとHtml用のビュー、Applicationメイラー
Userメイラーが生成される
user.mailer内にはアカウント有効化メソッド、パスワード再設定メソッドが自動で追加される

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
app/mailers/user_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のインスタンス変数はコントローラ同様、メイラーのビュー(text.erb , html.erb)でも利用できる
引数でuserを特定してそのアドレスへメールを送る

account_activation.text.erb
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) %>
account_activation.html.erb
<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) %>

注目すべきはこの一行
これはアカウント有効化するためのURLが生成している

edit_account_activation_url(@user.activation_token, email: @user.email)

今までを参考にすると

edit_user_url(user)

はusers/:id/editというURLが生成される

edit_account_activation_url(@user.activation_token, email: @user.email

ここでは、@user.activation_tokenを:idとみなしURLが生成され、その後にuserのアドレスを追加している
つまり、生成されるURLは**/account_activations/トークン/edit?メアド**となり
トークンはparams[:id]、メアドはparams[:email]で取得できる

:idの部分は実際にはnew_tokenのBase64で生成されているので

account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

となる

user_mailer_preview.rb
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

user変数にデータベース上の最初のユーザーを代入する

ユーザー登録を行う

users_controller.rb
  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

ユーザーを作成するアクション
UserMailer.account_activation(@user).deliver_now
では、user_mailer.rbで定義した

user_mailer.rb
def account_activation(user)
  @user = user
  mail to: user.email, subject: "Account activation"
end

メソッドを呼び出し、引数にとった@userにメールを送信する
redirect先がroot_urlなのは作成後にログインしないようにするためである

アカウントを有効化する

有効化するためには、アカウント有効化のダイジェストと渡されたトークンが一致するかチェックする

user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])

ただし、現時点ではremember_token用なので、正常に動作しない

# トークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

sendメソッド

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"

これを使えば、先ほどのauthenticated?もremember_tokenだけじゃなく他でも使えそう

user.rb
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

例えば、user.authencticated?(:activation, params[:id])としてeditアクションに組み込むと
第一引数の:activationはdigest = activation_digestとなり
第二引数のparams[:id]は有効化URLに含まれるトークンに値する
BCrypt::Password.new(digest).is_password?(token)
でdigestとトークンの比較を行う

account_activations_controller.rb
class AccountActivationsController < ApplicationController

  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
end

editアクションでは、
userをメアドで取得し、userが存在 -> activated == false -> activation_digest == activation_tokenであれば実行
user.activatedをtrueに更新
user.activated_atを現在時刻へ更新
userでログイン
userページへリダイレクト
という流れになる。ちなみn

!user.activated?

は既に有効であるユーザーを再度有効化にしないために追加した論理値である

あとは有効でないユーザーがログインしないようにすればいい

sessions_controller.rb
 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?を追加

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?