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
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.
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
Related
I'm trying to figure out a way to prevent someone from changing parts of the url. I've setup my website so that no one can sign up or create a account so anyone visiting is a guest.
For example, I've set the url in this part of the website to look for the value of 1a from /pathfinder/a/quest1/1a/q1sub1/ in order to display a certain part of the index page of q1sub1.
If someone were to change the value of 1a to 1n I would like to test if the url has changed and give some sort of error message.
Not sure what code excerpts to share in this case so let me know if you need more info.
I'm open to any ideas and appreciate any input. Thank you!
The, URL by design, can't be controlled by your application. But if you want to track where the user was previously, in all controller you have have access to request.referrer which should tell you where the request came from. You might add some conditional logic to your controller and redirect the user if the request.referrer is something you want to restrict. So in your controller action something like this:
def show # for example
if request.referrer != "http://example.com/myformpage"
redirect_to root_path, notice: "Invalid access"
else
# do stuff
end
end
However this is still not secure as headers can be spoofed (thanks to #Holgar Just). If you really want security in your application you should read the documentation and if you need more granular control over users permissions, maybe checkout CanCanCan gem
A way to do what you describe is to fingerprint the URLs before sending them to your client, using a hash function that includes a secret pepper. The idea is that if the client tampers with and URL the fingerprint won't match anymore, and since the fingerprint is generated with a server-side secret, the client won't be able to generate a new one that matches the new URL.
Here is an example.
The user will access /foo and pass a custom data parameter. The application will use this param to build a customized URL with format /my/secret/:one/path/:two, and redirect the client to it. This is just to keep things simple, and the same approach could be applied if the generated URL were to be used as a <a href="..."> in a page.
The generated URL contains a fingerprint, and if the client tampers with either the URL or the fingerprint, or if the fingerprint is missing, the application will respond with a 403.
Let's look at the code. The routes:
Rails.application.routes.draw do
get :foo, to: 'example#foo'
get '/my/secret/:one/path/:two', to: 'example#bar', as: 'bar'
end
And the controller:
class ExampleController < ApplicationController
# GET /foo?data=xx foo_path
#
def foo
user_data = request[:data]
go_to_path = bar_path(one: user_data, two: "foobar")
go_to_path += "?check=#{url_fingerprint(go_to_path)}"
redirect_to go_to_path
end
# GET /my/secret/:one/path/:two bar_path
#
def bar
unless valid_request?
render plain: "invalid request!", status: 403
return
end
render plain: "this is a good request", status: 200
end
private
SECRET = ENV.fetch("URL_FINGERPRINT_SECRET", "default secret")
# Calculate the fingerprint of a URL path to detect
# manual tampering.
#
# If you want to restrict this to a single client, add
# some unique identifier stored in a cookie.
#
def url_fingerprint(path)
Digest::SHA2.hexdigest(path + SECRET)
end
def valid_request?
return false unless params[:check].present?
params[:check] == url_fingerprint(request.path)
end
end
With this, the client would start with:
$ curl -i -s http://localhost:3000/foo?data=hello | grep Location
Location: http://localhost:3000/my/secret/hello/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
And then:
curl -i http://localhost:3000/my/secret/hello/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
HTTP/1.1 200 OK
this is a good request
But if the URL gets modified:
curl -i http://localhost:3000/my/secret/IWASCHANGED/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
HTTP/1.1 403 Forbidden
invalid request!
The same would happen if the fingerprint itself was modifed or if it was missing.
If a parameter that's required is missing using strong parameters, the Rails server will respond with an HTTP 500.
This does not give me control over giving the user feedback with what exactly went wrong. Does it not make sense to be able to send them back a message such a required parameter is missing?
What is the "Rails way" of giving appropriate user feedback on ActionController::ParameterMissing? Is one supposed to capture the exception and handle your request response there? It seems wrong to do that in every controller.
You can use
rescue_from ActionController::ParameterMissing do |e|
render 'something'
end
in your ApplicationController (or whatever your parent controller is).
As to whether you should inform users or not, I think it depends on what your controllers are doing. If they are API controllers, it definitely makes sense to handle that gracefully, as the users are responsible for preparing the input.
If they are accepting data from your HTML forms it's, in my opinion, not that important as the missing parameters probably mean that the user tinkered with the HTML, or something went really wrong inside the browser.
Since you mention wanting to communicate the error specifics back to the user, you could do something like the following:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing do |exception|
render json: { error: exception.message }, status: :bad_request
end
You can also define a method to handle a specific exception, if you'd prefer to break up the handling logic:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
def handle_parameter_missing(exception)
render json: { error: exception.message }, status: :bad_request
end
Both of the above examples will return a JSON response like so: {"error"=>"param is missing or the value is empty: [field_name]"}
For an API-only application, I think this is valuable information to pass on.
More info:
Rails API rescue_from documentation
Handling Errors in an API Application the Rails Way
I have a custom Class in my Rails application, which validates a bunch of settings.
The code:
class UserSettingObject < RailsSettings::SettingObject
validate do
if !/^([0-9]|0[0-9]|1[0]):[0-5][0-9]$/.match(self.time)
redirect_to settings_path, notice: 'Invalid time format'
end
end
end
I check SO posts and found a similar problem (here), the recommendation was include ActionController::Redirecting but it doesn't works, resulting in undefined method `config_accessor' error.
How to use Rails redirects methods in a custom Class?
In addition to what #SergioTulentsev already said, validations are for validating, not for taking actions. What you could do instead is leaving the regexp as a method, and in controller check the time using it and redirect based on the result of the validation.
Short answer is: you can't do that.
redirect_to only makes sense in request context (read: when being called from within controller action). You can't redirect from random objects, because they don't know what request to manipulate.
And yes, what #AndreyDeineko says.
So, I am somewhat new to rails and devise, so I apologize in advance if this is a basic question. I couldn't find any information on this anywhere, and I searched thoroughly. This also makes me wonder if Devise is the right tool for this, but here we go:
I have an app where devise user authentication works great, I got it, implemented it correctly and it works.
In my app, users can belong to a group, and this group has a password that a user must enter to 'join' the group.
I successfully added devise :database_authenticatable to my model, and when I create it an encrypted password is created.
My problem, is that I cannot authenticate this! I have a form where the user joins the group, searching for their group, then entering the password for it.
This is what I tried:
def join
#home = Home.find_for_authentication(params[:_id]) # method i found that devise uses
if #home.valid_password?(params[:password]);
render :json => {success: true}
else
render :json => {success: false, message: "Invalid password"}
end
end
This gives me the error: can't dup NilClass
on this line: #home = Home.find_for_authentication(params[:_id])
What is the problem?
The problem will be here:
Home.find_for_authentication(params[:_id])
I've never used database_authenticatable before (will research it, thanks!), so I checked the Devise docs for you
The method they recommend:
User.find(1).valid_password?('password123') # returns true/false
--
Object?
The method you've used has a doc:
Find first record based on conditions given (ie by the sign in form).
This method is always called during an authentication process but it
may be wrapped as well. For instance, database authenticatable
provides a find_for_database_authentication that wraps a call to
this method. This allows you to customize both database
authenticatable or the whole authenticate stack by customize
find_for_authentication.
Overwrite to add customized conditions, create a join, or maybe use a
namedscope to filter records while authenticating
The actual code looks like this:
def self.find_for_authentication(tainted_conditions)
find_first_by_auth_conditions(tainted_conditions)
end
Looking at this code, it seems to me passing a single param is not going to cut it. You'll either need an object (hence User.find([id])), or you'll need to send a series of params to the method
I then found this:
class User
def self.authenticate(username, password)
user = User.find_for_authentication(:username => username)
user.valid_password?(password) ? user : nil
end
end
I would recommend doing this:
#home = Home.find_for_authentication(id: params[:_id])
...
I am using the authenticate_or_request_with_http_digest method with rails 4, to let an admin sign in and perform some simple tasks. All links and sources at the bottom.
application_controller.rb
before_action :authenticate, only: :index # etc
def authenticate
authenticate_or_request_with_http_digest(CONFIG[:realm]) do |username|
session[:admin] = username
USERS[username]
end
end
This works as expected, however the default 401 page rendered when a user cancels the login process is terrible, it is a blank page with some text. I am using custom error templates as seen in Railscasts.com EP#53 and it would be great if I can use the same convention for this 401 error.
Is there a way to either amend the method below so displays a custom 401 page (Preferably without including all of the controller view and url helpers) or a simpler way altogether to achieve a dynamic 401 page?
ActionController::HttpAuthentication::Digest.module_eval do
def authentication_request(controller, realm, message = nil)
message ||= "You are unauthorized to access this page.\n"
authentication_header(controller, realm)
controller.response_body = message
controller.status = 401
end
end
Sources
Railscasts EP#53 Custom error templates: http://railscasts.com/episodes/53-handling-exceptions-revised -
authenticate_request method which seems to govern the 401 handling, source be seen in full at: https://github.com/rails/rails/blob/04cda1848cb847c2bdad0bfc12160dc8d5547775/actionpack/lib/action_controller/metal/http_authentication.rb#L244
authenticate_or_request_with_http_digest method and example can be seen here:
http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Digest.html#method-i-authentication_request.
Thanks to this great blog post about the subject the following code renders the errors/401 template gracefully. (Preferably clean up to use super rather than the deprecated alias_chaining method)
config/initializers/authentication.rb
ActionController::HttpAuthentication::Digest.module_eval do
def authentication_request_with_customization(controller, realm, message = nil)
message = controller.render_to_string(template: "errors/401")
authentication_request_without_customization(controller, realm, message)
end
alias_method_chain :authentication_request, :customization
end
Original post: http://p373.net/2013/11/16/how-to-customize-http-digest-authentication-error-pages-in-ruby-on-rails/