18

Flask-WTForms provides CSRF protection. It works great when using normal HTML forms, but the process is less clear when using AJAX. I have a file upload in my form, and I split the process in two with AJAX: the file goes to the upload endpoint while the rest of the form goes to the submit endpoint. Since the file is posted with AJAX, it doesn't get a CSRF token, but I want to protect the upload endpoint from attacks. How can I generate a CSRF token when using AJAX?

@app.route('/submit', methods=["GET","POST"])
@login_required
def submit():
    form = MyForm()

    if request.method == "POST" and form.validate():
        # success, csrf checks out and data is validated
        # do stuff

    csrf_for_uploads = # generate csrf?
    return render_template('some_form.html', form=form, csrf_for_uploads=csrf_for_uploads)

@app.route('/upload', methods=["POST"])
@login_required
def upload():
    myfile = request.files['file']
    # How do I verify CSRF now?
davidism
  • 121,510
  • 29
  • 395
  • 339
rublex
  • 1,893
  • 5
  • 27
  • 45

2 Answers2

28

The documentation speaks a bit about implementing CSRF protection with regards to AJAX.

You can enable the module:

from flask_wtf.csrf import CsrfProtect

CsrfProtect(app)

and then use this in your AJAX POST call:

<meta name="csrf-token" content="{{ csrf_token() }}">

var csrftoken = $('meta[name=csrf-token]').attr('content')

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken)
        }
    }
})

Hope this helps!

Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
Matt Healy
  • 18,033
  • 4
  • 56
  • 56
  • 3
    Hm, I completely missed that documentation. Yeah, that pretty much sums it up. – rublex Aug 08 '15 at 07:54
  • Hi please note: "FlaskWTFDeprecationWarning: "flask_wtf.CsrfProtect" has been renamed to "CSRFProtect" and will be removed in 1.0." – X.C. Oct 17 '17 at 23:30
  • 1
    May someone explain what "if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) " means? thanks! – X.C. Oct 17 '17 at 23:47
  • @X.C. if settings.type doesn't start with GET or HEAD ... and it's not a cross-domain request. – LeonF Nov 14 '17 at 16:50
  • 3
    this solution relies on rendering a template with jinja. is there a way to do it without? such as passing the token in the response? – Gil Hiram Apr 02 '18 at 14:28
  • 1
    how to do this using fetch API? – Reub Oct 14 '19 at 07:52
0

Im thinking !/^ is a negative assertion so if the request is not matching Get/Head etc and not cross domain, then set request header with the value of the csrf token