Gracefully handling InvalidAuthenticityToken exceptions in Rails 4 - ruby-on-rails

I've just upgraded an app from Rails 3 to Rails 4, and I'm seeing a bunch of InvalidAuthenticityToken exceptions popping up. Digging in it looks like it is fairly common for our users to have multiple long-lived tabs open on our site. So I think what's happening is something like this: user Alice has three tabs open and her session expires. She logs back in on one of the tabs, which updates the authenticity token stored in her session. Then she returns to one of the other open tabs and tries to submit data, but she gets a 500 error from the InvalidAuthenticityToken error we raised.
It would clearly be nice to do some error handling for Alice so she doesn't get a 500 error. I'm wondering about best practices for this kind of situation. What would be a nice way to handle Alice's submission from the expired tab? I don't want to expire the current session, because that would be super annoying from the user's perspective ("I just logged in, you dolt!"). Ideally, I just want the user to reload the page, which would result in the correct authenticity token being present in the form. Or should I be doing something different so that the long-lived tabs that are open notice that the session has expired and force a reload? This would probably be sub-optimal from the user's point of view, because they liked having that page ready and easily accessible to keep referencing, which is why they left it open in the tab in the first place.

In my rails 4.1.1 app I had the same problem. I solved it by adding this code to ApplicationController. I found this solution here.
rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_referer_or_path
def redirect_to_referer_or_path
flash[:notice] = "Please try again."
redirect_to request.referer
end
This way any controller that inherits from ApplicationController will handle the error with a redirect to the page the form was submitted from with a flash message to give the user some indication of what went wrong. Note this uses the hash syntax introduced in Ruby 1.9. For older versions of Ruby you will need to use :with => :redirect_to_referer_or_path

The solution to this problem can be divided into 2 phases. Phase 1 addresses the issue of ActionController::InvalidAuthenticityToken error and phase 2 deals with the issue of long tabs waiting idly.
Phase 1(1st variation)
One way to go about is redirect the user back to their location before the error. For ex. if Alice has 3 tabs open, the first one expires and Alice logs in again in it because she was browsing on it. But when she moves to tab 3 which has URL 'http://example.com/ex' and submits a form. Now instead of displaying her an error we can redirect her back to 'http://example.com/ex' with her submitted form values already pre-filled in the form for easy use.
This can be achieved by following this approach:
1) ApplicationController - Add this function:
def handle_unverified_request
flash[:error] = 'Kindly retry.' # show this error in your layout
referrer_url = URI.parse(request.referrer) rescue URI.parse(some_default_url)
# need to have a default in case referrer is not given
# append the query string to the referrer url
referrer_url.query = Rack::Utils.parse_nested_query('').
merge(params[params.keys[2]]). # this may be different for you
to_query
# redirect to the referrer url with the modified query string
redirect_to referrer_url.to_s
end
2) You need to include a default value for all your form fields. It will be the name of that field.
...
<% f.text_field, name: 'email', placeholder: 'Email', value: params[:email] %>
...
This way whenever Alice will submit a form with wrong authenticity_token she will be redirected back to her form with the original values she submitted and she will be shown a flash message that kindly retry your request.
Phase 1(2nd variation)
Another way to go about is just redirect Alice back to the form which she submitted without any pre-filled values.
This approach can be achieved by:
1) ApplicationController - Add this function:
def handle_unverified_request
flash[:error] = 'Kindly retry.'
redirect_to :back
end
Phase 2
To tackle the problem of long awaited tabs you can take the help of SSEs. Rails 4 has ActionController::Live for handling SSEs.
1) Add this to any controller:
include ActionController::Live
...
def sse
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, retry: 2000, event: 'refresh') # change the time interval to your suiting
if user_signed_in? # check if user is signed-in or not
sse.write('none')
else
sse.write('refresh')
end
ensure
sse.close
end
2) Give the above function a GET route in your routes file. Lets call this route '/sse'
3) Add this in your layout:
<% if user_signed_in? %> # check if user is signed-in or not
<script>
var evtSource = new EventSource("/sse");
evtSource.addEventListener('refresh', function(e){
if(e.data == 'refresh'){
window.location = window.location.href;
}
});
</script>
<% end %>
Note: using EventSource is not supported by all browsers. Please check out the Browser compatibility section.
Source:
rails 4 redirect back with new params & MDN: Using server-sent events

Answer by Durrell is perfectly alright, I am just providing an alternative way to write the same thing. This needs to go in ApplicationController.
rescue_from ActionController::InvalidAuthenticityToken do |_exception|
flash[:alert] = 'Please try again.'
redirect_back fallback_location: root_path
end

Related

When and how request sometimes found to be unverified. ruby on rails 3.2

# This is the method that defines the application behavior when a request is found to be unverified.
# By default, Rails resets the session when it finds an unverified request.
def handle_unverified_request
reset_session
end
I have seen this explanation at Rails 4 Authenticity Token
now my question is when and how every request sometimes become unverified? how it was hapenning? and when.
thankyou, i have tried to search it but i have seen explanation so deep-technical hence i can understand in an easy way
Rails adds a CSRF authenticity token to form submissions.
If you have a Rails-generated form in your browser and you inspect it, you'll see something like this:
<input type="hidden" name="authenticity_token" value="/LV6706J3W++oCASgg8+wuySgIksE9BNjamMbMW8Zv+G039yyxbpcRpUlUzuVbVvodKtDnUbknwo+jsBzsoO8g==">
Rails checks this hidden tag on form submission to make sure it's the same form that Rails generated in the first place. This helps prevent CSRF attacks
If this field's value doesn't match what Rails expects, it goes to the handle_unverified_request method you mentioned.
And it's not just forms, Rails can add tokens to the session to make sure it can match a request to an active session.
Regardless of the source, if Rails gets a mis-match, it wants to handle that as a security threat.
In essence, Rails is asking you "what should I do when I think the request I received is unverified and potentially an attack?"
In this case, Rails would reset_session which logs out the current_user.
Rails allows you to turn off or limit CSRF protection in cases where you may need to do strange things, but it's not advisable in any instances I'm familiar with.
You can do this by changing the options on protect_from_forgery as mentioned in the SO post you linked.
def handle_unverified_request
reset_connection
# validate only for html submit and not for ajax
if request.post? && !request.xhr? && request.content_type != 'multipart/form-data'
redirect_to controller: 'logout', action: 'index', is_invalid_token: true
end
return
end
and then i have log out controller
if !params[:is_invalid_token].nil?
flash[:notice] = "You dont have access with this."
flash[:notice_header] = 'Forbidden Access'
end
redirect_to :controller => 'login', :action => 'index'

How to validate my controller action was called from within my Rails app

I have a simple user registration form.
When the user fills out the form and clicks "Submit" I have a JavaScript event that intercepts the submit and uses AJAX to call the validate method below to check the form data before submitting. If the form data is ok it continues with the submit, but if it's not it cancels the Submit and displays a warning on the page. It's a simple way of doing real-time client side validations.
# app/controllers/users_controller.rb
# Validates the form data. Returns an error message response in the format
# { response: "Invalid email format!" }
#
# A blank response means success
#
def validate
if request.xhr? || <request came from another method in this controller>
# Run various validations on input data and determine a response
# response_text = ...
render json: { response: response_text }
else
redirect_to root_path
end
end
def create
if JSON.parse(validate)["response"].blank?
User.create(...)
# Other stuff
end
end
However when the submit does eventually pass and go through, it sends a POST to the create action to create a new User. That method calls validate again (the cost of re-validating is minimal) to ensure that no one bypassed the form and submitted a malicious request directly to the server.
So my validate method has to respond to both (a) AJAX calls and (b) "internal" calls from within the app. All other calls to the validate action should just redirect to the root path.
I can tell whether the call was an a AJAX call pretty simple using request.xhr?.
How do I check whether the action was called internally and not by a user?
Taking a step back, is this a good general approach for validation? Any thoughts to improve on it?
Thanks!
Rails generates an authenticity token whenever a user views a form and stores it as a random string in the HTML. That token is also stored in the session and is therefore invisible to the user. Upon receiving a request, your application will compare the tokens to verify whether the request was generated from your form.
TL;DR: Don't worry, you're already protected.
I don't have a specific answer, but do have some information which could help. I'll gladly delete this if required...
AJAX calls and "internal" calls
You have to understand that XHR requests can only come from your app -- CORS (cross origin resource sharing).
Ajax calls can only come from your own domain, so don't think a malicious hacker could run some scraper or whatever -- you choose which domains are permitted to send XHR requests.
So when you're calling...
if request.xhr?
... as part of a validation, you need to scope when XHR will be used.
On the same note, what validation are you performing?
If you're validating input data, Rails handles this at the model layer. We've done something similar (click login/register at top):
The base structure of Rails is MVC, which means that your controller is only responsible for taking a request & building the appropriate data objects out of it.
Thus, if you're "validating" an input, what is there to validate apart from the data itself (handled by the model)?
As #MarsAtomic specified, too, Rails uses a CSRF token to provide some level of authentication for a form submission.
--
You could easily use the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :js, :json, :html
def create
#user = User.new user_params
respond_with #user.save
end
end
If you then sent the request from your front-end as json, you'd get back the created user OR the errors:
#app/views/users/new.html.erb
<%= form_for #user, remote: true, format: :json do |f| %>
<%= f.text_field .... %>
<%= f.submit %>
<% end %>
#app/assets/javascripts/application.js
$(document).on("ajax:error", "#new_user", function(xhr, status, error) {
// do stuff with response
});
In your create method, you want to return a #user if it's not valid (if they skip the ajax validate).
If that's the case, you don't want to do User.create, instead, create an instance and try to save it. If you do all this, you're doing the normal controller action for create and there is no need to call validate internally.
def create
#user = User.new(user_params)
if #user.save
# Other stuff
end
end
And, it's not a good practice to create a json response and parse it to see if an object is valid. Instead, either test with valid? or just try to save and get back a true or false.
In other words - keep your validate method separate from your create method. They'll both rely on valid? eventually.

How to setup routes when the controller only has edit and update?

I can't seem to figure out how to get my routes setup properly.
In my app, I have a view that lets site owners update their address information. The new and create actions are part of the signup process and are located in the signups_controller. The edit and update actions are in the settings_controller.
When the user goes into the settings area, he/she sees only the edit form. When filled out, the user is then returned to the same form with a flash message, or error message. Here is what the controller looks like:
class SettingsController < ApplicationController
def edit
#account = current_account
#account.companies.first
#account.companies.first.addresses.first
#account.companies.first.phones.first
end
def update
#account = current_account
if #account.update_attributes(params[:account])
redirect_to edit_setting_path
flash[:notice] = "Success!"
else
render :edit
end
end
end
In my routes, I simply have:
resources :settings
The link to this area of the site is a basic RESTful named linke, with the parameter options:
edit_setting_path(:id => current_account.id)
When the user arrives to this page, they see the following URL:
http://domainname.com/settings/1/edit
When they submit the form and get errors, the URL changes to:
http://domainname.com/settings/1
Why is the URL changing -- I'd rather it not? Is there a way to make it stay the same as the initial edit view? I've tried doing a redirect on a failed update, but then I don't get the error messages.
Any ideas?
To answer your "why" question: The URL is changing because it's reflecting the URL of the failed request - which in this case is a PUT request to that URL (/settings/1). You've submitted the form and the submission of that form (correctly) points to that URL. This is a result of the RESTful routes that the helper gives you. Since the logic in your action, falls through to the render :action, there is no redirect and the form simply re-renders on the page using the same data available in this action (which is why you can see the errors).
If you want to redirect back to the edit page, yes, you will lose the errors that have been set in the #account instance variable since the redirect will reset (re-query for) the account.
You could add a route that matches a PUT to /settings/1/edit and point it to your update action and change your form etc. In short, I wouldn't recommend this, but it should work.
completely untested but attemptable:
routes.rb
put "/settings/:id/edit", :to=>"settings#update", :as=>"update_setting"
resources :settings, :except=>:update
your form would also have to submit to the update_setting_path (which also means it's not reusable for a new object... ew)
First you should read up on The Rails Guides for Routing. They will help a lot to understand why its working like that.
Secondly, to accomplish what you are trying to do, you will need to add manual routes via the match call. You'll need something like this.
match '/settings/:id/edit' => "settings#edit"

Devise. Registration and Login at the same page

I'm trying to integrate Devise into my application. I need implement login form at top of the page (I've implemented this form into layout page) and I've implemented registration which contains registration form.
But it shows validation errors for both form when I tried submit incorrect registration data.
Without more information, it's hard to guess what the problem is. I've found the Wiki pages to be really helpful (and increasingly so), though you may have already looked them over:
Devise Wiki Pages
Two pages that might be relevant to your needs:
Display a custom sign_in form anywhere in your app
Create custom layouts
Hope this helps!
-- ff
The problem of seeing the validation errors for both forms stems from the 2 things. First, devise forms use a generic 'resource' helper. This creates a User object, and that same user objet gets used for both the sign up and the sign in form. Second, devise errors are typically displayed using the 'devise_error_messages!' helper which uses that same shared resource.
To have sign in and sign up on the same page you need to create different user objects for each form, and a new way of displaying the error messages.
First off, you'll need to create your own registration controller (in app/controllers/users/)
class Users::RegistrationsController < Devise::RegistrationsController
include DevisePermittedParameters
protected
def build_resource(hash=nil)
super
# Create an instance var to use just for the sign up form
#sign_up_user = self.resource
end
end
And update your routes file accordingly
devise_for :users, controllers: {
registrations: 'users/registrations'
}
Next you'll need your own error messages and resource helpers. Create a new helper like devise_single_page_helper.rb and add the following:
module DeviseSinglePageHelper
def devise_error_messages_for_same_page(given_resource)
return "" if given_resource.errors.empty?
messages = given_resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
sentence = I18n.t("errors.messages.not_saved",
count: given_resource.errors.count,
resource: given_resource.class.model_name.human.downcase)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
def sign_up_user
#sign_up_user ||= User.new(username: 'su')
end
def sign_in_user
#sign_in_user ||= User.new(username: 'si')
end
end
Finally, in your views, update your forms like so:
-# The sign up form
= simple_form_for(sign_up_user, url: registration_path(resource_name)) do |f|
-#...
= devise_error_messages_for_same_page(sign_up_user)
-# The sign in form
= simple_form_for(sign_in_user, url: sessions_path(resource_name)) do |f|
#...
= devise_error_messages_for_same_page(sign_in_user)
All of this together gives you 2 different objects - 1 for sign up and 1 for sign in. This will prevent the error messages from one showing in the other. Please note that recommend putting both forms on your sign in page (and perhaps having the default sign up page redirect to the sign in page) because by default a failed sign in attempt will redirect to the sign in page.
You should have two forms on the page — one for signing up and one for registering. If you want a single form and multiple potential actions you are going to need a couple buttons that get handled client side and change the form's action & method to the appropriate route depending you want to create a user or a session.
If you think you did this already, the problem almost certainly lies in your code. If you were to share it with us we could perhaps point out something you may have missed.

authlogic - cause for unsuccessful session creation and no error messages (with formtastic)

I have 2 questions about authlogic:
1 - how to get cause of unsuccessful session creation (for example - not confirmed, blocked, wrong pass etc) to use it in app logic (route to another page for confimation, or re-enter pass etc)?
2 - question about integration formtastic and authlogic. How to show error messages on session creation , when login and pass is NOT provided. Everytime i submitting empty form - there are no any error message(but form.error_messages shows that you must enter login and pass), but if one of fields (login OR pass) provided - all works good.
1:
If you follow the suggested pattern for the login url:
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
redirect_to your_url
else
render :action => 'new'
end
end
The #user_session var will be available to your new template. You can access any login errors with #user_session.error_messages. I believe that returns formatted HTML, not an array.
UPDATE
According to the docs, Authlogic errors behave exactly like active record, so, in order to map your controller logic, you would do something like
if #user_session.save
# Normal flow
else
if #user_session.errors.on(:password)
# do something
else
# do something else
end
end

Resources