1

I'm working on a Rails API which will authenticate by means of a jwt, and would like the user accounts to be recoverable. Presumably, after receiving their reset token by email, users would supply their new password and the token with a PUT to https://example.com/users/password, to be handled by /app/controllers/users/passwords_controller#update. At present I've got this in passwords_controller.rb (mostly as in the default Devise code):

  # PUT /resource/password
  def update
    # {"user": {"password": "secret", "password_confirmation": "secret", "reset_password_token": "token_goes_here"}}
    self.resource = resource_class.reset_password_by_token(resource_params) # problem here (line 33)...
    yield resource if block_given?

    if resource.errors.empty?
      resource.unlock_access! if unlockable?(resource)
      if Devise.sign_in_after_reset_password
        resource.after_database_authentication
        auth_options = {user: {login: resource.username, password: params['user']['password']}}
        warden.authenticate!(auth_options)
        render json: {success: true, jwt: current_token, response: "Authentication successful" }
      else
        render json: {success: false, response: "Authentication failed" }
      end
    else
      set_minimum_password_length
      respond_with resource
    end
  end

The problem is that reset_password_by_token is requesting authorization:

 app/controllers/users/passwords_controller.rb:33:in `update'
Completed 401 Unauthorized in 220ms (ActiveRecord: 60.8ms | Allocations: 6810)

This seems odd to me as I'd expect the user to be able to log in once with the token (unless expired) for the password change. At that point I'd like to return the jwt (hence using warden.authenticate) so that the front-end application may immediately use it to sign the user in.

Would anyone be able to point me in the correct direction here?

knirirr
  • 1,860
  • 4
  • 23
  • 37

2 Answers2

0

Conceptually, the flow of the reset password functionality while using Rails as an API provider can be as follows:

  • User clicks on the reset password link on the front-end. The front-end makes an API call to POST /auth/password which sends an email to the user.
  • The link(from the user's mailbox) takes the user to the server(GET /auth/password/edit), where the token is verified, and then the server will redirect the user to the front-end with a token in the query params of the URL.
  • Using that token the front-end makes an API call to PUT /auth/password with params password and password_confirmation to set the new password.
Kartikey Tanna
  • 1,423
  • 10
  • 24
  • Thanks for your reply. Where I'm encountering the problem is in the last of the stages you mention. I think I might now have a solution but it needs a little more work. – knirirr May 17 '19 at 09:02
  • @knirirr were you able to fix it? I am also in the same problem atm. – LearningROR Mar 05 '22 at 02:50
  • Yes, though I don’t have the answer to hand right now - I’ll see if I can add it when I get time. – knirirr Mar 05 '22 at 08:43
  • Done, despite the annoying appearance of a captcha after I was already logged in. – knirirr Mar 05 '22 at 10:42
0

As the final result was requested in a comment, here it is:

  # PUT /resource/password
  def update
    resource = resource_class.reset_password_by_token(resource_params)

    if resource.errors.empty?
      resource.unlock_access! if unlockable?(resource)
      if Devise.sign_in_after_reset_password
        resource.after_database_authentication
        resource.skip_confirmation!  # Added in case user account shows as unconfirmed...
        sign_in(resource_name, resource)
        render json: {success: true, message: "Password reset successful." }
      else
        respond_with resource
      end
    else
      set_minimum_password_length
      respond_with resource
    end

  end
knirirr
  • 1,860
  • 4
  • 23
  • 37