Custom Devise 401 unauthorized response

12,768

Solution 1

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.

Solution 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

Share:
12,768

Related videos on Youtube

Matt Huggins
Author by

Matt Huggins

Currently developing with Ruby (Rails) and Javascript (React/Redux). Variety of past &amp; present experience includes mobile development (React Native, Android, Cordova), SQL, Java, PHP, C/C++, etc.

Updated on June 06, 2022

Comments

  • Matt Huggins
    Matt Huggins almost 2 years

    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
    
    • Mateusz
      Mateusz over 12 years
      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 }
  • Matt Huggins
    Matt Huggins over 12 years
    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
    Matt Huggins about 8 years
    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!
  • qix
    qix about 8 years
    Yes, I'm hoping this will save others some time. :)
  • Doug
    Doug over 5 years
    Thank you for this. Really saved me a lot of time.
  • BenFarhat Souhaib
    BenFarhat Souhaib over 4 years
    I tried to keep files organized and created this class under lib folder but seems like devise.rb cannot load file from lib
  • von spotz
    von spotz almost 3 years
    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!
  • qix
    qix almost 3 years
    @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') "
  • von spotz
    von spotz almost 3 years
    @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.