Ajax requests stop working after many successful requests - ruby-on-rails

In my Rails + Devise app I have a table of links to multiple "contacts", each being a simple jquery $.get AJAX request which calls Contact#show.
Inevitably after clicking anywhere from 3-25 of the links (successfully!), a request will fail (response status 0 or failed to load resource depending on browser), after which it will never work again until the browser tab is closed or cache is cleared.
Here's the javascript for the request
$.get('/contacts/1312')
Details...
I do have csrf_meta_tags at the top of my layout
The request heads do include a "X-CSRF-Token" with the correct CSRF token from the meta tag
On chrome, the failed requests do not show up in the server logs
as a request. its as if they never made it. The only error reported
is in the chrome console which reports a failure. This leads me to believe it is browser related.
On Safari, upon first failure, it seems to destroy the session any subsequent requests result in a request for the sign_in page, which leads me to believe it may have something to do with devise
Update 3/30/13: After looking at many of the related question on SO (this one: Rails not reloading session on ajax post), which have to do with CSRF not being set correctly, I dont believe this issue is related to CSRF because it works properly several times before it fails.

i eventually did figure it out. I was using a feature datatables.js (a table library) that saved its state in the cookie. However, the data it was trying to save in the cookie exceeded the 4kb max, so my cookie was getting messed up , resulting in different behaviours across different browsers.

Related

Grails Spring Security Last Request URL when AJAX

I'm recently facing the problem with last request url after login. Normally, everything works properly. But if I proceed following procedure I have troubles:
Open an App in one tab and log in
In this tab I go somewhere where AJAX request to the server are proceeded regularly
Open a new tab with the app (I'm still logged-in)
In this tab I log out
In the mean time the AJAX request from the 1st tab is proceeded automatically
with HTTP 401 (cause I've logged-out)
When I try to log in again in the 2nd tab than I receive the JSON of the AJAX request from the 1st tab because it was the last request.
I would suspect that Spring Security would neglect AJAX request for "last request url". Is it possible to set this somewhere? Or is there any good workaround to this?
Thanks,
Mateo
I don't think there is a general way to distinguish ajax requests from "regular" requests initiated by the user navigating a browser. If you can distinguish them in your application (e.g. by mapping ajax requests to specific urls that can be matched against some patterns), you could easily implement what you are looking for. (EDIT: An easier way to identify ajax requests is suggested by Sérgio in his below comment.)
The component that is responsible to perform redirection after a successful login is SavedRequestAwareAuthenticationSuccessHandler, therefore one possibile way to customize the framework's default behavior is to provide your own AuthenticationSuccessHandler, and inject it into the UsernamePasswordAuthenticationFilter. It should be possible to autowire the RequestCache in your class, and decide if you want to replay the last cached request or just ignore it in case it's known to be an ajax request.

HTML5 cache and authorization issues

I've got a problem after adding HTML5 caching to a singlepage app written in backbone on rails. Browser (chrome) isn't reloading the html and this causes problems in 2 ways:
1) The skeleton html for backbone sometimes changes - at very least to let backbone know that user is signed in. However with html not being loaded, the app doesn't know that (after refresh for example). I could query the server on page load, but that's another request which I hopped to avoid. Also this would force moving all the permisions logic to the client - so either duplicate cancan setting from the server OR embed it in html - and we run into above issue.
2) Rails' csrf tokens are in the html, and they too don't change - causing any ajax post to not work. For this one I'm not sure what to do. From what I read csrf token is generated for session so maybe on login/logout I could update it with js. However where to get it, would it work ?
i know this question is old but i just ran into the CSRF issue.
Try including the CSRF token as a comment in the manifest file. So whenever it changes it will change the manifest and force a reload.
CACHE MANIFEST
# include CSRF token in manifest to force reload when it changes
# <%= form_authenticity_token %>
to be safe you could run it through a hashing algorithm a few times

How to handle pages with stale CSRF authenticity tokens in Rails

In our Rails application, it is common for users to keep multiple browser tabs open for hours or days at a time. The problem occurs when in one of these tabs, the user logs out then logs back in (or the session expires and a new session is created).
This causes the CSRF authenticity tokens on all the other tabs to become invalid. If they try to submit any form or make any ajax request on those tabs without refreshing, they will get an error (and in fact get logged out because that is the default Rails behavior when a bad authenticity token is passed).
This behavior is clearly undesirable. I was wondering what people do to gracefully handle situations where a user has a window open to your site but the authenticity token is out of date.
What I don't want to do is just redirect them to the login page, because then they might lose their work, if for example they have been writng a long blog post or something.
The solution that comes to mind is to have some javascript that either polls the server to check whether the authenticity token has changed, or polls the user's cookies to check whether the session has changed. I have never heard of anyone doing either of these, so I wanted to see what the community thought.
First of: logging in/out/in won't lead to appearing a new csrf-token. It still will be saved in the user's cookie. Next time it logs in via the same browser it'll get the same token.
In latest versions of Rails no errors will be thrown in the case of incorrect token: all the Rails does -- just resets the session before passing it to a controller.
So, update your Rails and you'll get one pain less.
Are you sure you are talking about CSRF token and not session token? It does not make any sense at all to redirect to login on a CSRF token mismatch. You just tell the user to repeat whatever he tried to do. (In a traditional web application this typically comes up when a form is submitted; you can treat the CSRF mismatch as a validation error, and show the form again, keeping all the field values, and ask the user to resubmit. In a more AJAX-heavy application you can use some sort of generic CSRF flag in the response, and if it is set, ask the user to do whatever he did (press the button etc) once more, or even automate the whole thing without bothering the user.

Why does HttpAntiForgeryException occur randomly even with a static Machine Key?

We have an ASP.NET MVC 2 (.NET 4) application running on Windows Azure (latest 2.x OS version) with two web role instances.
We use the anti-forgery token supplied by MVC for all POST requests, and we have set a static Machine Key in web.config, so everything works on multiple machines and across restarts. 99.9% of the cases it works perfectly.
Every now and then, however, we log a HttpAntiForgeryException, with message "A required anti-forgery token was not supplied or was invalid."
I know the problem might be cookies not being allowed in the browser, but we've verified that and cookies are enabled and being sent back and forth correctly.
The error occurs with a variety of browsers and obviously causes problems to the users because they have to repeat the operation or they can lose some data. Suffice it to say, we haven't been able to reproduce the problem locally, but it only happens on Windows Azure.
Why is that happening? How can we avoid it?
I ran into this recently as well and found two causes.
1. Browser restores last session on open for page that is cached
If you have a page that is cachable that performs a post to your server (i.e. antiforgery will be on) and the user has their browser set to restore last session on start up (this option exists in chrome) the page will be rendered from cache. However, the request verification cookie will not be there because it is a browser session cookie and is discarded when browser is closed. Since the cookie is gone you get the anti-forgery exception. Solution: Return response headers so that the page is not cached (i.e. Cache-Control:private, no-store).
2. Race condition if opening more than one tab on start up to your site
Browsers have the option to open a set of tabs at start up. If more than one of these hit your site that returns a request verification cookie you can hit a race condition where the request verification cookie is overwritten. This happens because more than one request hits your server from a user that does not have the request verification cookie set. The first request is handled and sets the request verification cookie. Next the second request is handled, but it did not send the cookie (had not been set yet at request time) so the server generates a new one. The new one overwrites the first one and now that page will get an antiforgery request exception when it next performs a post. The MVC framework does not handle this scenario. This bug has been reported to the MVC team at Microsoft.
The anti forgery token contains the username of the currently connected user when it is emitted. And when verifying its validity, the currently connected user is checked against the one used when the token was emitted. So for example if you have a form in which the user is not yet authenticated and you emit an anti forgery token, there won't be any username stored in it. If when you submit the form you authenticate the user, then the token will no longer be valid. Same applies for logging out.
Here's how the Validate method looks like:
public void Validate(HttpContextBase context, string salt)
{
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string str2 = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
HttpCookie cookie = context.Request.Cookies[str2];
if ((cookie == null) || string.IsNullOrEmpty(cookie.Value))
{
throw CreateValidationException();
}
AntiForgeryData data = this.Serializer.Deserialize(cookie.Value);
string str3 = context.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(str3))
{
throw CreateValidationException();
}
AntiForgeryData data2 = this.Serializer.Deserialize(str3);
if (!string.Equals(data.Value, data2.Value, StringComparison.Ordinal))
{
throw CreateValidationException();
}
string username = AntiForgeryData.GetUsername(context.User);
if (!string.Equals(data2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw CreateValidationException();
}
if (!string.Equals(salt ?? string.Empty, data2.Salt, StringComparison.Ordinal))
{
throw CreateValidationException();
}
}
One possible way to debug this is to recompile ASP.NET MVC from its source code and log exactly in which of the if cases you enter when the exception is thrown.
I have a few MVC3 web apps that get this pretty regularly also. The majority of them are because the client doesn't send a POST body. And most of these are IE8 because of some bug with ajax requests preceding a regular form post. There's a hotfix for IE that seems to address the symptoms, which sort of proves that it is a client bug in these cases
http://support.microsoft.com/?kbid=831167
There are a few discussions about the issue around the web, nothing too useful though, I definitely am not about to mess with keep-alive timeouts which is a suggested "solution" in some places...
https://www.google.com/search?q=ie8+empty+post+body
I've never been able to reproduce it with a variety of attempts to reset connections between POSTS so I'm afraid I don't have a real solution for the case of the IE empty POST bodies. The way we've mitigated it a little bit is to make sure that we never use the POST method when just retrieving data via ajax.
If you log the full request, check to see if the POST body is empty, and if it is, it'll probably be an older IE. And I don't mean Content-Length: 0, it will usually have a Content-Length that seems correct in the headers but there will literally be nothing after the headers in the request.
The issue as a whole is still a mystery to me though because we still get the occasional exception where there is a complete POST body. Our usernames never change and our keys are static as well, I haven't tried adding debugging to the source, if I ever get around to that I will report my findings.
There are a couple of options for what you could try. You could try remoting into the machine and looking at the event log to see if you can get more information from that in regards to where this is happening. If that doesn't help, you can use DebugDiag or some other tool to capture a dump of the process (DebugDiag will let you capture one at the time of this specific exception). And then look at that to see what is going on.
If you can't seem to figure it out from there, you can always create a support case with Microsoft to help you investigate it.
I have encountered similar problems with my home-brewed anti-forgery code, which is conceptually very similar to the MVC mechanism. Mostly the problem seems to occur because modern browsers appear willing to display cached copies of pages specified as non-cached.
I have tried all combinations of page no-cache directives, but sometimes I still get cached pages displayed.
I have found that a better solution is to hook the onbeforeunload event for the page and explicitly clear the value of the hidden input field holding the token value in the DOM.
If a cached copy of a page is loaded, it seems to contain the cleared input field value. I then test for this in the document ready function and reload the page if necessary:
window.location.reload(true);
Seems to work quite effectively, and I suspect it might for the MVC anti-forgery code too.

In an ASP.NET MVC 3 application, how can I save the post data in the where their session times out

I have an ASP.NET MVC 3 application. The site involves people writing lengthy responses using a textarea in a web form. Occasionally, users are complaining that they are getting redirected to the log in form after they post their data. I am not sure exactly why they are getting logged out because the users do not typically provide enough information on their errors. I believe it is due either to a session time out or the application has been restarted for some reason. This is on a shared web hosting site and it does not have its own app pool.
In any case, regardless of the reason, I would like to capture that post data and save it to a db or text file. How can I get the post data and save it while the controller redirects the user to the login screen.
I know the long term plan would be to identify why the timeout is occurring. But for now I want to be able to grab the post data and recover it at a later time.
First, in order to avoid timeouts, I would recommend using client-side heartbeat solution (like http://plugins.jquery.com/project/Heartbeat)
Second, assuming that you are using forms authentication, in order to save posted data, when Forms Authorization Module is redirecting your users, you will need to intercept redirects in EndRequest HttpApplication event handler in Global.asax or your own module.
The way to intercept those requests is not that straightforward, since on "EndRequest" pipeline step you will see 302 HTTP status code (redirect instruction), not 401 (Unauthorized error). So you may check if request is not authenticated (HttpContext.User.Identity.IsAuthenticated) and request is redirected - in this case you may save what you see in the request.
Otherwise you would need to disable forms authentication and use some solution, which is closer to ASP.NET MVC.
one solution can be to put a javasscript timer which keeps on hitting the server after specified interval to keep session alive until u figure out the cause of session time out (only i its the session timeout problem)
If you want to stop the session from timing out, you can add a hidden iframe on the page. For example, create a new page called KeepSessionAlive and do this:
<meta http-equiv="refresh" content="600">
where content = seconds.
I don't know about MVC 3, but the way you can get and store the post values is to catch them before redirecting the user to the Login page.

Resources