23

I'm working on a JSON-based API for my Rails 3.1 app. I'd like to provide a custom failure response instead of the default, which is:

{"error":"You need to sign in or sign up before continuing."}

My API controller includes a before_filter call to authenticate_user!, which is what is rendering this JSON response.

While searching, I came across this StackOverflow question, which references this Devise wiki entry. Unfortunately, the wiki entry isn't verbose enough for me to understand what it's telling me. Specifically, I have no clue where I'm supposed to put that code such that Devise/Warden knows to render what I want returned.

From the comments on the other SA question, it sounds like I don't need to call custom_failure! since I'm using a version of Devise above 1.2 (1.4.2 to be specific). However, the wiki entry doesn't explain where the render call should go such that authenticate_user! knows to use that instead of its own render call.

Where does this render call go?

Edit: I'm not just trying to change the message itself (a la the devise en.yml config); I'm trying to change the actual format of the response. Specifically, I want to return this:

render :text => "You must be logged in to do that.", :status => :unauthorized
Community
  • 1
  • 1
Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
  • did you succeed? for ok answer I overwrite SessionController#create method but have no idea where to put this `format.json { render :json => { "status" => ERROR_NOT_REGISTERED }, :status => :unauthorized }` – Mateusz Dec 17 '11 at 16:30

2 Answers2

32

For reference in case anyone else stumbles upon this question when looking for how to customize the json error response when a failed login attempt is made using Devise, the key is to use your own custom FailureApp implementation. (You can also use this approach to override some redirect behavior.)

class CustomFailureApp < Devise::FailureApp
  def respond
    if request.format == :json
      json_error_response
    else
      super
    end
  end

  def json_error_response
    self.status = 401
    self.content_type = "application/json"
    self.response_body = [ { message: i18n_message } ].to_json
  end
end

and in your devise.rb, look for the config.warden section:

  config.warden do |manager|
    manager.failure_app = CustomFailureApp
  end

Some related info:

At first I thought I would have to override Devise::SessionsController, possibly using the recall option passed to warden.authenticate!, but as mentioned here, "recall is not invoked for API requests, only for navigational ones. If you want to customise the http status code, you will have better luck doing so at the failure app level."

Also https://github.com/plataformatec/devise/wiki/How-To%3a-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated shows something very similar for redirection.

qix
  • 7,228
  • 1
  • 55
  • 65
  • I no longer have this question, but based upon the number of votes my question received, your answer will obviously help a lot of people. Thanks for sharing! – Matt Huggins Feb 09 '16 at 19:31
  • Yes, I'm hoping this will save others some time. :) – qix Feb 09 '16 at 19:55
  • Thank you for this. Really saved me a lot of time. – Doug Jul 30 '18 at 04:37
  • I tried to keep files organized and created this class under `lib` folder but seems like devise.rb cannot load file from lib – BenFarhat Souhaib Aug 07 '19 at 20:30
  • Hello, where to put the FailureApp ? In the Devise wiki the folder lib/devise/failure is mentioned, but it doesn't yet exist in my project structure. Does this require to have some generator run ? Thanks! – von spotz May 06 '21 at 06:23
  • 1
    @vonspotz Devise wiki also mentions "If you’re getting an uninitialized constant CustomFailure error, and you’ve put the CustomFailure class under your /lib directory, make sure to autoload your lib files in your application.rb file, like below `config.autoload_paths << Rails.root.join('lib')` " – qix May 31 '21 at 10:16
  • @qix Thanks I already put that path in my autoload_path, but there is no further documentation of FailureApp, how to use it. It seems you can only overwrite the methods and I couldn't even make a call to `redirect_to` in my subclass and my overwritten method. So now I am trying OAuth with the `Identity` gem and other auth methods. It seems more flexible. – von spotz May 31 '21 at 10:53
2

If you're simply wanting to change the text displayed with the error message, I believe you can just edit the locale file (/config/locales/devise.en.yml).

The RailsCast on this topic might be helpful too, if you want more specific details. You can find it at http://railscasts.com/episodes/210-customizing-devise

Robert
  • 504
  • 5
  • 27
ksambrook
  • 161
  • 4
  • Thanks for pointing that out. I've been using a custom error response in that locale file in my project to date, but I'm actually looking to change the format of the response in this situation. I've updated the question to include this. – Matt Huggins Sep 06 '11 at 16:21