we have a simple form. From time to time some people (which we don´t know) produce the following error we get in appsignal.
HTTP request error: invalid CSRF (Cross Site Request Forgery) token, make sure all requests include a valid '_csrf_token' param or 'x-csrf-token' header
We know one user who sometimes has this error and sometimes not. How can that be?
It seems that mostly IE / Safari (ipad, iphone) / Opera users are affected. How can we debug what happens?
Other than noting the pages (where the forms were submitted) and attempting to reproduce it with exactly the same browser version, there is little you can do to reproduce it, I’m afraid.
The form is in an iframe. Is it possible that the way an iframe gets treated in Internet Explorer 11 can change the csrf token?
Yes, definitely, this is a known issue with IE11.
- Is it a multipart form?
- What is the response content-type of the submission handler?
I also see the same issue from time to time.
Usually the offending CSRF token is the same on all the failed requests.
Not sure what the user is doing, I couldn’t replicate it yet.
I’d like to pitch in as well. We are also seeing this error occurring regularly. I did some investigation and it seems the token is tied to the session of the user, so the only reproduction scenario I can think of is opening the form, clearing the session on the client, and then submitting the form. I find it highly unlikely though that this is what the user is actually doing. Is there any other behaviour surrounding the CSRF token that we need to be aware of? Does it expire after a certain amount of time for instance?
@laurens how is your session stored? If you are using ETS, every server restart will clear all sessions. Any login pages rendered with the old session will raise this exception.
No, we are using the cookie store. As far as I understand from the documentation, the token should remain valid until I change the secret key base.
Or until the internal lifetime is elapsed (which in the next phoenix version defaults to 1 day I think, instead of the like 1 year or whatever it defaults to now, though it is best to set is explicitly regardless).
Same issue here.
Happened 425 times in the last 3 months, which makes it happen for 1% users roughly. I agree with @Max diagnostics: seems to be only on mobile.
I hardly can imagine 1% of the users coming back 1 day later to browse the website, so IMHO there must be another case.
To debug, this would be nice if we could differentiate:
- when the csrf token is not sent at all
- when it is expired
- when it is faked
Would that seem possible?
Also, from the docs what are the risks of setting
:clear_session option instead of
Sorry to reply to this old post. But I have been having this issue in production as well. same as @augnustin, I am having this InvalidCSRFTokenError around 1% off our request(or event a bit higher). So far we can’t see any pattern. It looks completely random and we are using standard cookies storage.
I will appreciate if any of you found a way to solve this issue or at least know the root cause?
This issue happens mostly because of how browsers purge sessions but keep pages. Here is most likely a way to reproduce it:
- Load a page containing the form
- Quit the browser software completely but ask it preserve pages
- Reopen the browser
- Submit the form
Between quitting and reopening the browser, the session was purged, but the now outdated CSRF token is still in the form. The browser does not ping the server at all, so there is nothing we can do to address this in a way that won’t also make applications vulnerable.
The long term answer is the SameSite cookie attribute, but it will be a while until it is standardized. You can also set protect_from_forgery to clear_session but it still means that the form submission won’t work.
I ran into the same kind of issue. What was happening in our application was the following:
- User submits a form
- User goes back to previous page (using browser/history back button)
- User submits the same form again
When “loading” the previous page in step 2, the browser would load the page from cache, with a now stale/invalid CSRF token.
I fixed it by adding http headers to the response in a plug:
def put_no_cache_headers(conn, _) do
|> put_resp_header("cache-control", "no-store, max-age=0")
|> put_resp_header("pragma", "no-cache")
|> put_resp_header("expires", "0")
This forces the browser to request the page from the server again, now with a fresh CSRF token.