Devise: Use password reset mechanisms with JSON-only communication

Created on 6 May 2013  路  13Comments  路  Source: heartcombo/devise

We are developing a backend with pure JSON communication in Rails and use Devise for user authentication. Unfortunately, the password reset functions don't seem to support JSON at all. I've tried to implement the following in a custom controller.

    def create
      @user = User.send_reset_password_instructions(params[:user])
      if successfully_sent?(@user)
        head :status => 200
      else
        render :status => 422, :json => { :errors => @user.errors.full_messages }
      end
    end

Route used for this:

scope '/api/v1' do
    devise_for :users, :skip => :sessions, :controllers => { :passwords => 'api/v1/users/passwords' }
end

The successfully_sent method was copy&pasted from the DeviseController but still, instead of printing just status 200, it renders devise/mailer/reset_password_instructions.html.erb which confuses the JSON client.

Is there any way to make the password workflow support JSON?

Most helpful comment

Congratulations on your first contribution!

All 13 comments

The default Devise controller is supposed to work just fine, you just need to call in your ApplicationController (or in your DeviseController child) respond_to :json to tell it should also return json responses.

You are right, thank you. Maybe I was confused what it needs new_user_session_path for. I don't use sessions so I skipped the session routes in devise_for. Does resetting work only with sessions enabled or is there some trick?

I would consider the fact it needs new_user_session_path for APIs a bug. :) Could you provide a fix + test case?

The issue happens because the same URL we use to redirect for HTML is also being used for JSON:

https://github.com/plataformatec/devise/blob/master/app/controllers/devise/passwords_controller.rb#L49

We should probably check if it is a navigational format or not and return something different. This also happens in other controllers. :S

You mean something like...

def after_sending_reset_password_instructions_path_for(resource_name)
    if is_navigational_format?
        new_session_path(resource_name)
    else
        # Do nothing?
    end
end

Or even..

def after_sending_reset_password_instructions_path_for(resource_name)
        new_session_path(resource_name) if is_navigational_format?
end

Don't know if that's even valid. Just trying to contribute to this great gem here. ;-)

I think that would be fine, yeah. As long as we are not returning a 201 nor a redirect, we don't need to add the Location header.

If it is not asking too much, would you mind sending pull requests for fixing this issue in the other controllers too? Confirmations and unlocks controller have the same issue. Thank you!

Sure thing. Hope I'll do this right. First contribution to an open-source project, I'm excited.

https://github.com/plataformatec/devise/pull/2407

I hope, this does the trick. Unfortunately, I don't know how to test this, but in theory, it should cover the relevant API functions.

Just checked: PUT /resource/password works fine without any changes with JSON only. If the password reset works, it just returns 204 No Content

edit: Right, we are trying to solve the dependency on session_path. That it works with sessions enabled is not surprising, nevermind.

Congratulations on your first contribution!

Glad I could help!

Was this page helpful?
0 / 5 - 0 ratings