質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.22%

Rails でユーザ認証を実装したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 223

ts21

score 15

前提・実現したいこと

Railstutrialを模倣したRailsのWebアプリケーションを開発しています。
HerokuのSendgridアドオンを用いてユーザ登録の際にメール認証機能を実装している過程です。
登録情報をパラメータにしてメールを送信、受け取ることまではできていまいす。

発生している問題・エラーメッセージ

メールが届き、Activateリンクを踏んでも認証されていないという問題が発生して困っています。
リンクの生成が正しく行われていないのかと思います。
ログはこんな感じです。

Started POST "/users" for ::1 at 2019-11-14 12:26:51 +0900
Processing by UsersController#create as */*
  Parameters: {"user"=>{"user_name"=>"ts", "email"=>"ts@icloud.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}
   (1.4ms)  BEGIN
  ↳ app/controllers/users_controller.rb:25
  User Exists (1.3ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2  [["email", "ts@icloud.com"], ["LIMIT", 1]]
  ↳ app/controllers/users_controller.rb:25
  User Create (2.0ms)  INSERT INTO "users" ("email", "password_digest", "activation_digest", "user_name", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETU
RNING "id"  [["email", "ts@icloud.com"], ["password_digest", "$2a$12$tp4dd/HD7L81zvxaVrNnhOyI1JOBt/300zLEQbiV56rJ9JXqxuN/a"], ["activation_digest", "$2a$12$551NN
cMiWkr9seluACIDj.xcmyji5DrMPWXIjqZepoVi42KOCXrPO"], ["user_name", "ts"], ["created_at", "2019-11-14 12:26:51.396443"], ["updated_at", "2019-11-14 12:26:51.396443"]]
  ↳ app/controllers/users_controller.rb:25
   (3.0ms)  COMMIT
  ↳ app/controllers/users_controller.rb:25
  Rendering user_mailer/account_activation.html.erb within layouts/mailer
  Rendered user_mailer/account_activation.html.erb within layouts/mailer (4.4ms)

  Rendering user_mailer/account_activation.text.erb within layouts/mailer
  Rendered user_mailer/account_activation.text.erb within layouts/mailer (0.8ms)
UserMailer#account_activation: processed outbound mail in 18.7ms
Sent mail to ts@icloud.com (972.6ms)

Date: Thu, 14 Nov 2019 12:26:51 +0900
From: noreply@example.com
To: ts@icloud.com
Message-ID: <5dccc97b9f939_c5e93ff5410a0d68858@ts21.local.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_5dccc97b9e4cc_c5e93ff5410a0d68846d";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_5dccc97b9e4cc_c5e93ff5410a0d68846d
Content-Type: text/plain;
 charset=UTF-8

Content-Transfer-Encoding: 7bit

Hi ts,

Welcome to the Sample App! Click on the link below to activate your account:

https://aqueous-escarpment-97262.herokuapp.com/account_activations/63/edit?email=ts%40icloud.com&token=-u7w-JfyU21-SgpvcQ1FWQ
https://account_activations/63/edit?email=ts%40icloud.com&token=-u7w-JfyU21-SgpvcQ1FWQ


----==_mimepart_5dccc97b9e4cc_c5e93ff5410a0d68846d


Postmanで確認してみるとこんな感じになります

{
    "status": "success",
    "data": {
        "id": 63,
        "email": "ts@icloud.com",
        "password_digest": "$2a$12$tp4dd/HD7L81zvxaVrNnhOyI1JOBt/300zLEQbiV56rJ9JXqxuN/a",
        "remember_digest": null,
        "admin": null,
        "activation_digest": "$2a$12$551NNcMiWkr9seluACIDj.xcmyji5DrMPWXIjqZepoVi42KOCXrPO",
        "activated_at": null,
        "reset_digest": null,
        "reset_sent_at": null,
        "user_name": "ts",
        "display_name": null,
        "address": null,
        "avatar_uri": null,
        "created_at": "2019-11-14T12:26:51.396+09:00",
        "updated_at": "2019-11-14T12:26:51.396+09:00",
        "activated": false,
    }
}

該当のソースコード

user_controller.rb

class UsersController < ApplicationController
  before_action :set_user, only: [:update, :destroy]

  # before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
  #                                       :following, :followers]
  # before_action :correct_user,   only: [:edit, :update]
  # before_action :admin_user,     only: :destroy

  def create
    @user = User.new(user_params)
    if @user.save
     @user.send_activation_email
     render json: { status:  "Please check your email to activate your account." }
    else
      render json: { status: 'error!', data: @user.errors }
    end
  end

  def edit
  @user = User.find(params[:id])
end

  def update
    # @user = User.find(params[:id])
    if @user.update(user_params)
      render json: { status: 'success', data: @user }
    else
      render json: { status: 'error', data: @user.errors }
    end
  end

  def destroy
    # User.find(params[:id]).destroy
    # flash[:success] = "User deleted"
    # redirect_to users_url
    @user.destroy
    render json: { status: 'success', data: @user }
  end

  private

  def user_params
    params.require(:user).permit(:user_name,:email,:password,:password_confirmation)
      # @users = params[:user].permit(:email, :password, :password_confirmation)
  end

  def set_user
    @user = User.find(params[:id])
  end
end


account_activation_controller.rb

class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    token = params[:token]
    if user && !user.activated? && user.authenticated?(:activation, :token)
      user.activate
      log_in user
      render json:{status:"Account activated!"}
    else
      render json:{status:"Invalid activation link"}
    end
  end
end


account_activation.text.erb

Hi <%= @user.user_name %>,

Welcome to the Sample App! Click on the link below to activate your account:

<%= edit_account_activation_url(@user,
                               token: @user.activation_token,
                               email: @user.email) %>


account_activation.html.erb

<h1>Sample App</h1>

<p>Hi <%= @user.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,
                                                    token: @user.activation_token,
                                                    email: @user.email) %>


user.rb

class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest

validates :user_name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
                  format: { with: VALID_EMAIL_REGEX },
                  uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

  def User.digest(string)
  cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                BCrypt::Engine.cost
  BCrypt::Password.create(string, cost: cost)
  end


    # Returns the hash digest of the given string.
    def User.digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                    BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end

    # Returns a random token.
    def self.new_token
      SecureRandom.urlsafe_base64
    end

    # Remembers a user in the database for use in persistent sessions.
    def remember
      self.remember_token = User.new_token
      update_attribute(:remember_digest, User.digest(remember_token))
    end

    # Returns true if the given token matches the digest.
    def authenticated?(attribute, token)
      digest = send("#{attribute}_digest")
      return false if digest.nil?
      BCrypt::Password.new(digest).is_password?(token)
    end

    # Forgets a user.
    def forget
      update_attribute(:remember_digest, nil)
    end

    # Activates an account.
    def activate
      update_attribute(:activated,    true)
      update_attribute(:activated_at, Time.zone.now)
    end

    # Sends activation email.
    def send_activation_email
      UserMailer.account_activation(self).deliver_now
    end

    # Sets the password reset attributes.
    def create_reset_digest
      self.reset_token = User.new_token
      update_attribute(:reset_digest,  User.digest(reset_token))
      update_attribute(:reset_sent_at, Time.zone.now)
    end

    # Sends password reset email.
    def send_password_reset_email
      UserMailer.password_reset(self).deliver_now
    end

    # Returns true if a password reset has expired.
    def password_reset_expired?
      reset_sent_at < 2.hours.ago
    end

    # Returns a user's status feed.
    def feed
      following_ids = "SELECT followed_id FROM relationships
                       WHERE  follower_id = :user_id"
      Micropost.where("user_id IN (#{following_ids})
                       OR user_id = :user_id", user_id: id)
    end

    # Follows a user.
    def follow(other_user)
      following << other_user
    end

    # Unfollows a user.
    def unfollow(other_user)
      following.delete(other_user)
    end

    # Returns true if the current user is following the other user.
    def following?(other_user)
      following.include?(other_user)
    end

    private

      # Converts email to all lower-case.
      def downcase_email
        self.email = email.downcase
      end

      # Creates and assigns the activation token and digest.
      def create_activation_digest
        self.activation_token  = User.new_token
        self.activation_digest = User.digest(activation_token)
      end
  end


Invalid activation linkが表示されます。
account_activation_controller.rbのif文の中身がおかしいのかと思っています
何かお気づきになりましたら教えていただきたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

if user && !user.activated? && user.authenticated?(:activation, :token)
と (:activation, :token) で authenticated? を 呼んでますが
def authenticated?(attribute, token) で受けた方は
.is_password?(token) にて params[:token]の値を欲しがってます。

digest = send("#{attribute}_digest") これが謎です。
digest = send("attribute_digest") ってことをやってます。
digest = self.attribute_digest ってことです。

digestは activation_digest に置いてあるのでしょう?
digest = self.attribute_digest
でよいのでは?

ということで多分 user.authenticated?(:activation, token)
引数が一つ不要に思えますが

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/15 17:44

    回答ありがとうございます。
    シンボルを消してみましたがうまくできませんでした。

    キャンセル

  • 2019/11/15 18:48

    エラーメッセージは?

    キャンセル

  • 2019/11/15 20:51

    値が
    def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    logger.debug("digest=#{digest}, token=#{token}") #<===
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
    end
    とでもして、期待通りの値が渡っているかみてみたらどうでしょう

    キャンセル

  • 2019/11/16 11:20

    ローカルから立ててたサーバにスマホからメールの認証をしようとしていたことが大きな間違えでした。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる