I'm really having trouble with this, and in this instance, I neither want to skip the verify_authenticity_token
filter, nor change to protect_from_forgery with: :null_session
.
In my request method, I am setting a header with the csrf token as follows:
var token = document.querySelector("meta[name='csrf-token']").content;
xhr.setRequestHeader("X-CSRF-Token", token);
And by inserting a breakpoint in my controller like so:
def verify_authenticity_token
binding.pry
super
end
I have verified that the header is set:
[1] pry(#<MyController>)> request.headers
=> #<ActionDispatch::Http::Headers:0x007fb227cbf490
@env=
{"CONTENT_LENGTH"=>"202",
.
.
.
# omitted headers
.
.
.
"HTTP_X_CSRF_TOKEN"=>"the-correct-token-from-meta-tag",
.
.
.
}
I have also tried passing the token as a param with the key authenticity_token
(as is done with Rails forms), and set the X-CSRF-Param
tag to match (from meta[name="csrf-param"]
).
Yet I am still getting:
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 14638ms
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken
Anyone seen this before? Any thoughts on what might cause this?
Thanks in advance!
EDIT:
Following discussion in the comments of marflar's answer, it looks like the token has expired when the request is made (tested by comparing to form_authenticity_token
). This is confusing me further, as the token set in <%= csrf_meta_tags %>
is expired when the next request comes in. Any thoughts?
EDIT2: Following marflar's advice below, I added the following after_filter
to my app controller:
def set_csrf_headers
response.headers['X-CSRF-Param'] = request_forgery_protection_token.to_s
response.headers['X-CSRF-Token'] = form_authenticity_token
end
And I updated xhr.onload
in my request method as follows:
namespace.request = = function (type, url, opts, callback) {
// code omitted
xhr.onload = function () {
setCSRFHeaders(xhr);
var res = {data: JSON.parse(xhr.response), status: xhr.status};
return callback.call(xhr, null, res);
};
// code omitted
}
function setCSRFHeaders ( xhr ) {
var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
var csrf_token = xhr.getResponseHeader('X-CSRF-Token');
if (csrf_param) {
document.querySelector("meta[name='csrf-param']").content = csrf_param;
}
if (csrf_token) {
document.querySelector("meta[name='csrf-token']").content = csrf_token;
}
}
I verified that the response headers, and then the meta tags are getting reset properly, however, by the time the next request comes in, this new token is expired again. Thoughts?
I have the same problem. I inspected Rails source code and concluded next:
authenticity_token
not expired by itself, so no need to update it after each ajax request to serverparams[:authenticity_token]
and header['x-csrf-token']
, just one of them, rails will check params
first, than header
authenticity_token
will be different, but it doesn't matter, because it's generated each time with one time pad and real csrf token (on the server) is time independentsession[:_csrf_token]
As you can see token is kept in session and my problem was that my session was expired after 24h (probably user stays on page for day without refresh)
If user is logged in by cookie or some other token params, anyway he gets new session and with it new CSRF token will be generated and any request with old authenticity_token
will be invalid.
So, the main problem is with session, it's expired or reset.
My guess is that Rails might expect the token to be in the HTML, not the header. Can you try that? Hope it helps.
Actually I think you might be using a stale CRSF token because you're copying it from your template.
I normally set it like so in my controller action:
response.headers['X-CSRF-Param'] = "#{request_forgery_protection_token}"
response.headers['X-CSRF-Token'] = "#{form_authenticity_token}"
Does the token in your page match the one returned by calling form_authenticity_token
?
UPDATE
In response to your comment (quoted below):
I just checked and you are right about it being a stale token, which unfortunately leaves me even more confused. The meta tags with the CSRF data are set on the initial page load, at which time they match form_authenticity_token, yet the token is stale by the time the first ajax request is made. So it won't matter whether I set them in the HTML or as headers, as this would occur at the same time, and thus run into the same issue with the token expiring before the next request is made. Thanks for your help so far -- any ideas here?
I ran into this sort of problem when implementing AJAX login. I found I was unable to make POST requests of any kind after logging in, and that I needed the following code to refresh my token:
var update_csrf_token_and_param_after_ajax_login = function() {
$(document).on("ajaxComplete", function(event, xhr, settings) {
var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
var csrf_token = xhr.getResponseHeader('X-CSRF-Token');
if (csrf_param) {
$('meta[name="csrf-param"]').attr('content', csrf_param);
}
if (csrf_token) {
$('meta[name="csrf-token"]').attr('content', csrf_token);
}
});
}
I think you probably just need to write a fresh token into your page before doing the POST...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With