ユーザ登録にメール認証によるアカウント有効化機能を付加したときの流れ


目的

Webアプリケーションへのユーザ登録の際、入力されたEメールが本当に存在するかを確認した上でユーザ登録する機能を実装しました。
そのときのユーザ側の動きとサーバ側の動きをまとめました。
コードは認証失敗などの処理が無い前提で使う部分だけ記載しています。
※Railsチュートリアル11章の内容です。

処理の流れ

1.ユーザ側で登録する情報を入力してもらう。その際、Eメールは必須項目。

/users/newで下記画面が出力。

2.ユーザが送信ボタンを押したとき、新しいユーザレコードが作成されサーバ側でtokentとdigestを作成

・Tokenはユーザ端末に保存またはユーザが入力する鍵となる値
・digestはtokenを元に生成される不可逆的なハッシュ値。サーバ側に保存される。

#新しいユーザが作成される(User.createが実行される)際は
#before_createによりcreate_activation_digestが実行

#Userモデル
class User < ApplicationRecord
  before_create :create_activation_digest

  def create_activation_digest
    #Tokenの生成
    self.activation_token = User.new_token
    #digestの生成
    self.activation_digest = User.digest(activation_token)
  end

  # 渡された文字列のハッシュ値を返す
  # BCryptのGEMを使用(gem 'bcrypt')
  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

end

#Usersコントローラ
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    #ここで上記のUser.create_activation_digestが実行
    if @user.save
       @user.send_activation_email
      #省略
    else
      #省略
    end
  end
end

#またUserモデル
class User < ApplicationRecord
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
    #selfはUsersControllerの@user
  end
end

#Userメーラー
class UserMailer < ApplicationMailer
  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end
end

上記によりユーザへメールが送付される。

3.ユーザが入力したEメールアドレスに、サーバがアカウント認証用のEメールを送付する。

・Eメールの本文には、EメールアドレスとTokenを含めたURLへのリンクが記載されている。

"Activate"のリンクには下記のURLへリンクが貼られている。

https://fqdn/account_activations/<Token>/edit?email=<Email>
例:https://fqdn/account_activations/T5oNDqEPDZKQGzrIje3OjA/edit?email=test%40email.com

ユーザは、このEメールアドレスとTokenが含まれたURLをクリックしてアクセスする。つまり、ブラウザのアドレスバーにEメールとTokenを入力してサーバに送信することとなる

4.サーバ側で入力されたTokenからdigestを生成、そして入力されたEメールをキーにしてユーザを参照する。

URLに含まれるPathは"account_activations/:id/edit"の形となるので
AccountActivationsコントローラのeditアクションが実行される。

5.参照したユーザのレコードに保存されているdigestとユーザが入力したTokenから生成したdigestが一致することを確認。

#AccountActivationsコントローラ
class AccountActivationsController < ApplicationController
  def edit
    user = User.find_by(email: params[:email])
    #URLに含まれるEメールアドレスをキーにしてユーザ情報を取得

    if user && !user.activated? && user.authenticated?(:activation, params[:id])
    #ユーザが存在する、かつ、ユーザのactivatedカラムがtrueではない、かつ、ユーザ認証が成功したときを条件として、ユーザを有効化
    #.activated?はカラム"activated"を指す。.authenticated?はUserモデルのメソッドを指す
    #":activation"は下に記載するUserモデルのメソッドで
    #  activation_digestのメソッドの文字列を作るメタプログラミングのための引数
      user.activate
      log_in user
      #省略
    else
      #省略
    end
  end
end

#またまたUserモデル
class User < ApplicationRecord
  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

  #アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end
end

#Sessionsヘルパーモジュール
module SessionsHelper
  #渡されたユーザでログインする
  def log_in(user)
    session[:user_id] = user.id #:user_idはハッシュキー
  end
end

#補足:Sessionsヘルパーモジュールを他のコントローラでも使えるようにApplicationコントローラで定義
class ApplicationController < ActionController::Base
  include SessionsHelper #セッションコントローラ以外でも使えるようにする
end

コントローラとかモデルとかメーラーとかヘルパーとか行ったり来たりでこの流れを理解して覚えるのはちょっと時間かかるかなー。。。