How to use Flask-WTF CSRF protection for Single Page Apps? - python

I noticed that tokens generated by Flask-WTF expire in one hour and a different one is generated every request. This will cause problems in SPAs when a page has been opened longer than an hour. XHR requests made after one hour after page-load will start failing, even if the user was active.
My workaround is to set a new token in the browser in each API call. In the server, all API responses contain a freshly-generated token:
from flask_wtf.csrf import generate_csrf
def api_response(data, error=None):
response = {"csrftoken": generate_csrf(), "data":data}
...
return make_response(jsonify(response), response_code)
In the browser we set the csrftoken on each API response.
then(function(result) {
if(result.csrftoken) csrftoken=result.csrftoken;
callback(result);
})
Is this method still safe and fast? Is there a better way to handle this? I am not too sure about using generate_csrf directly.

No, there is no other way to use the CSRF protection in Flask-WTF. When you need a CSRF token, you need to generate one and use it. There should be no problem with generating it like you do. It is still generated and validated the same way on the server, and transmitted over the same channel to the client.

Related

Storing and "replaying" flask requests

I have a flask system that will redirect users to a login page if they are not logged in. However, after logging them in, I'd like to send them right back to whatever request they were making before I interrupted them with the login page.
For GET requests, this is relatively trivial, since all the important information is in the URL. But for a POST request, it's not so trivial, as there are a number of different properties one might want to access from the request proxy.
I would like to know whether I can somehow store/save a request (e.g., in a before_request function) before sending them to the login page, and essentially "replay" their original request again after they log in.
My current strategy has been to JSONify as much of the original Request object as I could, store it in a cookie, and then modify the request proxy in before_request to give it the properties of the previous request.
Although I can modify the request properties via something like request.endpoint = "index", I can't seem to use setattr(request, "endpoint", "index") to set the values programmatically.
Does anybody know how to do that? Or, how one might get around this problem?

Removing header from cached response with NGINX

I have NGINX running as a reverse proxy in front of a few Flask apps.
I want to implement caching for logged out users.
Flask-login adds a Set-Cookie header for every response, even for anonymous users, as it contains a session cookie with a CSRF token. This means that that I'm using proxy_ignore_headers Set-Cookie; to ensure that stuff actually get's cached by NGINX (which won't cache and response with a Set-Cookie header).
I'm setting a separate cookie in the apps to indicate the logged in/out status of a user and using that to determine whether to use the cache or not. This works great.
The issue is that the cached responses for a logged out user include the Set-Cookie header which sets the session cookie. This session cookie is served to any request that hits the cache, which ultimately results in different users receiving the same CSRF token.
I would like to either prevent the Set-Cookie header being stored in the cache, or remove/overwrite it when it's sent to the client from the cache.
I've tried setting proxy_hide_headers Set-Cookie which removes it from cached responses, but also from responses from that app. So no one can log in. Which is bad.
It feels like there should be a really easy solution to this, I just can find it no matter how hard I google.
Any help is appreciated.
Update: After trying a million things I have a solution that’s working for multiple cookies, I would like your opinions.
On Debian 10 I installed apt-get install libnginx-mod-http-lua I think this is not the complete OpenResty lua-nginx-module, isn’t it?
map $upstream_bytes_received $hide_cookie {
default '';
'' Set-Cookie;
}
Inside location:
header_filter_by_lua_block {
ngx.header[ngx.var.hide_cookie] = nil;
}
And it works, I will do more testing...
Previous answer, for 1 cookie, without Lua:
I've been working on a solution for this, but for now it works for ONLY ONE cookie.
First I faced the following problems: $proxy_hide_header does not accept variables, and cannot be used inside if().
I finally found an answer that contained a viable solution to that: Using a Header to Filter Proxied Response Headers.
So this is my code for now , that I will test more, because is a delicate matter:
map $upstream_bytes_received $cookies {
default $upstream_http_set_cookie;
'' '';
}
And then inside location:
proxy_hide_header Set-Cookie;
add_header Set-Cookie $cookies;
Maybe I would make the default: No cookies, that will be noticeable if fails, and less problematic regarding privacy.
But this solution I think cannot be improved for multiple cookies, I have to look elsewhere, if I could force the use of variables at $proxy_hide_header would be the end solution.

Strange session behaviour with a Flask app on Heroku

I have a web application that uses GitHub's OAuth API in order to allow the app's users to interact with GitHub. However, I'm seeing some very odd behaviour with regards to the session cookie.
As a bit of background, I am using peewee to interface with Heroku's Postgres server, and have a User model like so:
class User(peewee.Model):
login = peewee.TextField(unique=False)
token = peewee.TextField()
I am using the web application flow described in the GitHub OAuth documentation, and am successfully getting called back with an access token, which I store in the database, and also in the session [1]:
#app.route('/callback')
def finishlogin():
# I've verified that `token` and `login` are both valid at this point
user = User.create(login=login, token=token)
session['token'] = token
return redirect(url_for('home'))
My route for home is as follows:
#app.route('/')
def home():
if 'token' in session:
user = User.get(token=session.get('token'))
return 'Your login is {}'.format(user.login)
else:
# ...
So far, so good, and this works correctly. However, I am experiencing instances of users logging in, refreshing the page and finding that they are suddenly logged in as someone else. Logging the requests to the app shows that on the second request the session cookie itself has sent the wrong value (i.e. session.get('token') in home() returns a valid, but incorrect value. Clearly the user's browser can't know any other session value, so it seems that there is some "leakage" in setting the session between different clients and requests.
I'm not sure what the problem might be. My database is stored on the Flask g object as described in the peewee docs and has before_request and teardown_request hooks set up to open and close the database connection, and from all the documentation and example code I have read (and I've read a lot!), I seem to be using the session object correctly. I have set up a working secret_key for the session store.
I'm wondering if this could be something going on with Heroku and their routing mesh? But then, how would one user suddenly send another user's session?
Any hints or advice would be appreciated—I've been staring at this for a long time and am at my wits' end.
[1] I'm aware that storing the token directly is a bad design choice. The application is non-public and this will be fixed, but for now I want to describe the problem as it exists, even though it's not ideal.
Answering my own question for future reference.
It seems that this was being caused by Flask's default session cookie behaviour, which is to send a Set-Cookie header with every single request, even for static assets. Our local Squid proxy was therefore gladly caching those requests and re-issuing Set-Cookie headers for every user.
Setting Cache-Control headers for the whole app seems to fix the issue.

CSRF error django rest framework when using session auth

I am building an api for interfacing with mobile applications that requires users to login. Just using session auth gives me a csrf error. I was able to fix it by providing credentials in basic auth, but I don't think this is ideal. This will be my first time developing for mobile devices. I was planning on using cordova, and I don't know if there is a way to store credentials on the user's device, or if the session data will be automatically stored on the devices. If the session data will be stored on the mobile devices automatically, that would be the ideal route to go. Has anyone else had similar issues with DRF session auth, or advice on if this is the best route to go or not?
Update:
I was able to get the csrf error to go away by using this from another post:
from rest_framework.authentication import SessionAuthentication
class NoCsrfSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return
But this seemed to cause an error with the request.data parameter. It kept returning an empty query dict.
If you want to use session auth, but are confident that you can give up CSRF protection for a given view, the
#csrf_exempt
decorator will do just that. (If you are using class-based views, check out this: https://stackoverflow.com/a/14379073/1375015)
Since you are using session based authentication, your mobile applications must be storing some kind of session cookie. Therefore, you should also be able to store the csrftoken cookie and send it along with your http requests. However, even then I had some troubles with the django CSRF protection framework in the past.
Maybe switching to token authentication is an option?

Django: Forcing CSRF token on all responses

My website has an AJAX POST view that can be called from any page on the app (event tracking). This view is protected by CSRF. In some cases, the CSRF cookie is not set, and the POST call fails.
Instead of manually decorating all views with #ensure_csrf_cookie, I'm thinking of writing I created a middleware that enforces Django to set the CSRF cookie on all responses. Is this approach correct? Does it create a security flaw I'm not aware of?
Update: here is the middleware code:
from django.middleware.csrf import get_token
class ForceCsrfCookieMiddleware(object):
def process_request(self, request):
get_token(request)
No, there is no problem as long as you're not rendering the csrf token inside a form that posts to an external site (but that would be a problem anyways, no matter where you implement it). You can set it on a middleware, or some views, or on all views, it doesn't matter.
The CSRF protection is only made to ensure that the request is coming from your site. No matter how often you set the cookie, if the request includes the correct CSRF token it means that the request is indeed coming from your site, because only your site can access your cookies. (of course this only holds if you are not leaking the CSRF token to third parties, for example by sending it to other sites)
In few words, this is how it works:
The server sets a cookie with a random value in the response
Your site reads that value and sends it to the server when posting data
Since cookies can only be accessed from the same domain that set them, there is no way for another site to read that cookie. Therefore, whenever you receive a request that has the right csrf token, you are assured that that request is coming from your site.
For a very good explanation of CSRF, have a look at this article: http://www.gnucitizen.org/blog/csrf-demystified/

Categories