[Day13 Rails Tutorial]第十一章完|ユーザー認証メールを実装して感動[11.2〜11.5]

Day13です!

もうそろそろチュートリアルも終盤。

今日もがんばっていきましょー!

読んでいただいている方、いつもありがとうございます!

初心者なもので、たまに間違った内容もあるかと思います。

その際にはコメントやTwitterでご指摘いただけると幸いです!

 

[Day13]第十一章11.2〜11.5作業ログ

実際にメールを送る機能の実装の準備

今回は実際にユーザーの有効化のメールを送る機能を作っていきます。

メイラーを下のコマンドで作成。

rails generate mailer UserMailer account_activation password_reset

実行すると、いろんなファイルができます。

  • application_mailer.rb
  • user_mailer.rb
  • account_activation.text.erb
  • account_activation.html.erb

下2つは、メールのテキストバージョンと、HTMLバージョンそれぞれのviewファイル。

また、今回必要なaccount_activationメソッドと、パスワードのリセットに必要なpassword_resetメソッド(第十二章でやる)が生成される。

このファイルたちを編集していきます。

application_mailer.rbにはメールの送り主を設定。

返信されても困るので、noreply@example.comにしておきましょう。よくありますよね。

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  layout 'mailer'
end

user_mailer.rbでは、送信先と件名(Subject)を設定。

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

 

ユーザー有効化のためのURLの設定

メールの本文に書きたい、ユーザー有効化のためのURLの設定を行います。

名前付きルートは下の表のようになっていました。

edit_account_activation_url(@user.activation_token, ...)

これに対応する、アカウント有効化のURLはこうなります。

http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit

ここで、アカウント有効化は、こんな流れで行うのでした。

  1. ユーザーの初期状態は「有効化されていない」(unactivated) にしておく。
  2. ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
  3. 有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく3
  4. ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
  5. ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。

メールで送ったリンクで有効化が完了するには、トークンだけでなくユーザーのメールアドレスを含める必要があります。

これは、クエリパラメータというものを使って設定します。

クエリパラメータとは、URLの末尾で疑問符「?」に続けてキーと値のペアを記述したものです

つまり、有効化のURLの後半はこんな感じになればOK。

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

ここで”%40″は@のこと。

これはエスケープという技術で、通常URLに含められない記号を変換してURLに使っている。

こんなクエリパラメータ付きのURLを作成するには、アクションに2つの引数を渡せばOKらしいです。

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

よって、メールの本文はHTML版ではこうなります。

app/views/user_mailer/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) %>

チュートリアル内では、送信メールのプレビューを簡単にチェックできるツールが紹介されていました。

ここではカットします。

 

メールを自動送信するように設定

新規登録した時に、アカウント有効化メールを自動送信するようにしましょう。

これは実はものすごく簡単です。

アカウントを作成して、saveする時に、下のようなコードをかませてあげるだけ。

class UsersController < ApplicationController
  ...
  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
  ...
end

ついでにフラッシュメッセージとリダイレクト先も変えたことに注意。

 

アカウントを有効化するメソッドの作成

これでアカウントを有効化するURLを送信できました。

あとはそのURLからアカウント有効化をしてくれるメソッドの作成をします。

ですが、これは記憶トークンを記憶トークンダイジェストと認証したauthenticated?メソッドとほとんど同じになります。

そこでちょっと寄り道して、共通でauthenticated?メソッドが使えるようにします。

 

sendメソッドでアクションを抽象化

sendメソッドを使って、authenticated?メソッドを抽象化します。

抽象化するというのは、『メタプログラミング』という技術を使って、プログラムでプログラムを作成できるようにするということです。

よくわからないので、ちょっと詳しく見ます。

例えば、配列の長さをはかるlengthメソッドとsendメソッドをチェインしてみます。

$ rails console
>> a = [1, 2, 3]
>> a.length
=> 3
>> a.send(:length)
=> 3
>> a.send("length")
=> 3

ここで、下2つについてもきちんとlengthメソッドが働いていることに注目。

このsendメソッドの引数として渡した、シンボル:lengthや文字列”length”を勝手に認識して、オブジェクトにlengthメソッドを渡してくれています。

これを応用すると、authenticated?メソッドの使用範囲をぐっと広げられます。

もともとのauthenticated?メソッドはこうでした。

user.rb
def authenticated?(remember_token)
  digest = self.send("remember_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(remember_token)
end

sendメソッドで改良すると次のようになります。

user.rb
def authenticated?(attribute, token)
  digest = self.send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

引数も改名されています。

このauthenticated?メソッドでは、2つの引数をとることができ、第一引数の中身を生かして、メソッドを呼び出すことが可能。

よって、remember_digestとactivation_digestを使い分ける事ができるようになりました。

引数の数が変わったので関係あるテストコードはしっかり変えておきましょう。

 

editアクションで認証機能を実装

準備が整ったので、editアクションで認証機能を実装してあげます。

app/controllers/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

ここで、!user.activated?はアクティベートされていないユーザーであることを確認しています。

これがないと、認証メールを盗まれたとときに、いつでもログインされてしまう。

これがあれば、認証がおわれば、その認証URLからはログインできなくなります。

 

ログイン方法を設定してアカウントの有効化を有効化

今のままだと、ログイン方法を変えていないので、認証がまだでも普通にログインできてしまいます。

user.activated?がtrueの場合にのみログインを許可し、そうでない場合はルートURLにリダイレクトしてwarningで警告するようにしましょう。

sessions_controller.rb
class SessionsController < ApplicationController
  ...
  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
  ...
end

このあと、テストやコードのリファクタリング等ありましたが、割愛。

 

本番環境でのメールの送信設定

やっと最後!

本番環境つまりHerokuからアカウント認証メールを遅れるようにしましょう。

これはHerokuが用意してくれている『starter tier』という機能を使います。

heroku addons:create sendgrid:starter

このコードで使えるようになります。

あとは、production環境のSMTPに情報を設定。

config/environments/production.rb
Rails.application.configure do
  ...
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  host = '<your heroku app>.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :address        => 'smtp.sendgrid.net',
    :port           => '587',
    :authentication => :plain,
    :user_name      => ENV['SENDGRID_USERNAME'],
    :password       => ENV['SENDGRID_PASSWORD'],
    :domain         => 'heroku.com',
    :enable_starttls_auto => true
  }
  ...
end

ここで太字にしたように、ユーザー情報には必ず環境変数を用いて、そのままuser_nameとpasswordを書かないようにしましょう。

正直この環境変数あたりがぼくめちゃくちゃ弱い。

今回はHerokuのSendGridアドオンが自動的に設定してくれるそう。

以前インターンでLINEbotを作った時に、API情報をべた書きされて注意されたなぁ。有料のAPIだったら大金請求される可能性あるって教えてもらった思い出。

 

さいごはadd、commit、pushして終了!

heroku run rails db:migrateでHerokuでデータベースを回すのも忘れずに。

本番環境からしっかり認証メールが送られてくるようになりました!

感動の極み!!!!!

おつかれさまでした!

 

[Day13]まとめ

  • 学習範囲:11.2〜11.5
  • 学習時間:4時間20分
  • 総学習時間:55時間20分
  • 反省点:演習の沼にまたハマってた。潔くあきらめて先人の答えを見るのもアリ。時間を大切に。
  • 備考:細かいところはちょびちょび飛ばすことにした。

読んでいただいている方、いつもありがとうございます!

初心者なもので、たまに間違った内容もあるかと思います。

その際にはコメントやTwitterでご指摘いただけると幸いです!

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です