web-dev-qa-db-ja.com

Rails 4 + Devise:実稼働サーバーでパスワードリセットが常に「トークンが無効です」エラーを表示しますが、ローカルで正常に動作します。

Deviseを使用するためにセットアップされたRails 4アプリケーションがあり、パスワードリセットで問題が発生しています。メーラーがセットアップされ、パスワードリセットメールが正常に送信されます。そのデータベースで確認した正しいreset_password_tokenが割り当てられていますが、正しい形式のパスワードでフォームを送信すると、リセットトークンが無効であるというエラーが表示されます。

ただし、Rails sを使用すると、まったく同じコードがローカルで正常に機能します。電子メールが送信され、実際にパスワードをリセットできます。私が使用するコードは標準のDeviseコードであり、オーバーライドしていません。

おそらくそれはApacheの何かでしょうか?私はそれにあまり詳しくありません。誰にもアイデアはありますか?

48
justindao

_app/views/devise/mailer/reset_password_instructions.html.erb_のコードを確認します

リンクshouldは次のもので生成されます:

edit_password_url(@resource, :reset_password_token => @token)

ビューがまだこのコードを使用している場合、それが問題の原因になります。

edit_password_url(@resource, :reset_password_token => @resource.password_reset_token)

Deviseはトークンのハッシュの保存を開始したため、メールはデータベースに保存されたハッシュ値ではなく、実際のトークン(_@token_)を使用してリンクを作成する必要があります。

この変更は、 143794d701 のDeviseで発生しました

126
doctororange

Doctororangeの修正に加えて、resource.find_first_by_auth_conditionsを上書きする場合は、warden_conditionsにメールまたはユーザー名の代わりにreset_password_tokenが含まれる場合を考慮する必要があります。

編集:詳しく説明するには:

Deviseは、「devise:registerable、:trackable、...」と言うと、モデルに機能を追加します。

ユーザーモデル(または管理者など)で、find_first_by_auth_conditionsという名前のDeviseメソッドを上書きできます。この特別なメソッドは、ログインを試行しているレコードを見つけるためにDeviseロジックによって使用されます。 Deviseは、warden_conditionsというパラメーターの情報を渡します。これには、電子メール、ユーザー名、reset_password_token、またはデバイスのログインフォームに追加したもの(アカウントIDなど)が含まれます。

たとえば、次のようなものがあります。

(app/models/user.rb)
class User

  ...

  def self.find_first_by_auth_conditions warden_conditions
    conditions = warden_conditions.dup

    if (email = conditions.delete(:email)).present?
      where(email: email.downcase).first
    end
  end

end

ただし、deviseはトークンを使用してレコードを検索するため、上記のコードはパスワードリセット機能を無効にします。ユーザーは電子メールを入力せず、URLのクエリ文字列を介してトークンを入力します。この文字列はこのメソッドに渡され、レコードを検索しようとします。

したがって、この特別な方法を上書きする場合は、パスワードリセットのケースを考慮して、より堅牢にする必要があります。

(app/models/user.rb)
class User

  ...

  def self.find_first_by_auth_conditions warden_conditions
    conditions = warden_conditions.dup

    if (email = conditions.delete(:email)).present?
      where(email: email.downcase).first
    elsif conditions.has_key?(:reset_password_token)
      where(reset_password_token: conditions[:reset_password_token]).first
    end
  end

end
11
MaximusDominus

ログからURLを取得している場合、次のように表示されます。

web_1      | <p><a href=3D"http://localhost:3000/admin/password/edit?reset_password_to=
web_1      | ken=3DJ5Z5g6QNVQb3ZXkiKjTx">Change password</a></p>

この場合、3DJ5Z5g6QNVQb3ZXkiKjTxは実際にエンコードされた=3D文字であるため、=をトークンとして使用しても機能しません。

この場合、J5Z5g6QNVQb3ZXkiKjTxを使用する必要があります(3Dは削除済み)

5
ybart

受け入れられた答えは正しいですが、なぜこれが起こっているのかを説明したいので、他のいくつかのケースでも使用できます。パスワードリセットトークンを生成しているメソッドを見ると:

def set_reset_password_token
    raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)

    self.reset_password_token   = enc
    self.reset_password_sent_at = Time.now.utc
    self.save(validate: false)
    raw
end

rawが返され、encがデータベースに保存されていることがわかります。データベースの値を使用している場合-encを使用してpassword_reset_tokenをフォームの非表示フィールドに入力すると、常にToken invalidは暗号化されたトークンです。使用する必要があるのは、rawトークンです。

これは、一部の管理者(またはハッカー)がデータベースにアクセスできる場合、管理者は暗号化されたトークンを使用するだけで簡単にリセットできるためです。

これに関するいくつかの情報とDeviseのその他の変更は、 deviseのchange-logブログの投稿 または deviseの問題の議論 で見つけることができます。

0
Aleks

カスタム確認メーラービューを使用している場合は、(@ doctororangeの投稿に加えて)以下に注意する価値があるかもしれません。

ビューのリンクもここで変更されました。これは新しいリンクコードです。

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

これは古いリンクコードです。

<p><%= link_to 'Confirm my account', user_confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p>
0
pixelearth