What seemed like a simple bug - a form submission that won't go through due to a "CSRF token missing" error - has turned into a day of hair pulling. I have gone through every SO article related to Flask or Flask-WTF and missing CSRF tokens, and nothing seems to be helping.
Here are the details:
Following Martijin's guidelines to an earlier question:
The Flask-WTF CSRF infrastructure rejects a token if:
1) the token is missing. Not the case here, you can see the token in the form.
The token is definitely present in my form, and being POST'ed successfully
2) it is too old (default expiration is set to 3600 seconds, or an hour). Set the TIME_LIMIT attribute on forms to override this. Probably not the case here.
Also OK for me - the token is well within the default expiration time
3) if no 'csrf_token' key is found in the current session. You can apparently see the session token, so that's out too.
In my case, session['csrf_token'] is properly set and seen by Flask
4) If the HMAC signature doesn't match; the signature is based on the random value set in the session under the 'csrf_token' key, the server-side secret, and the expiry timestamp in the token.
This is my problem. The HMAC comparison between the submitted form's CSRF and the session CSRF fails. And yet I don't know how to solve it. I've been desperate enough (as with the other questioner) to dig into Flask-WTF code and set debugging messages to find out what's going on. As best I can tell, it's working like this:
1) generate_csrf_token()
in "form.py" (Flask-WTF) wants to generates a CSRF token. So it calls:
2) generate_csrf()
in "csrf.py". That function generates a new session['csrf_token'] if one does not exist. In my case, this always happens - although other session variables appear to persist between requests, my debugging shows that I never have a 'csrf_token' in my session at the start of a request. Is this normal?
3) The generated token is returned and presumably incorporated into the form variable when I render hidden fields on the template. (again, debugging shows that this token is present in the form and properly submitted and received)
4) Next, the form is submitted.
5) Now, validate_csrf
in csrf.py is called. But since another request has taken place, and generate_csrf() has generated a new session CSRF token, the two timestamps for the two tokens (in session and from the form) will not match. And since the CSRF is made up in part by expiration dates, therefore validation fails.
I suspect the problem is in step #2, where a new token is being generated for every request. But I have no clue why other variables in my session are persisting from request to request, but not "csrf_token".
There is no weirdness going on with SECRET_KEY or WTF_CSRF_SECRET_KEY either (they are properly set).
Anyone have any ideas?