Flask and Ajax Post HTTP 400 Bad Request Error

10,627

Solution 1

If you are using the Flask-WTF CSRF protection you'll need to either exempt your view or include the CSRF token in your AJAX POST request too.

Exempting is done with a decorator:

@csrf.exempt
@app.route("/json_submit", methods=["POST"])
def submit_handler():
    # a = request.get_json(force=True)
    app.logger.log("json_submit")
    return {}

To include the token with AJAX requests, interpolate the token into the page somewhere; in a <meta> header or in generated JavaScript, then set a X-CSRFToken header. When using jQuery, use the ajaxSetup hook.

Example using a meta tag (from the Flask-WTF CSRF documentation):

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

and in your JS code somewhere:

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

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

Your handler doesn't actually post JSON data yet; it is still a regular url-encoded POST (the data will end up in request.form on the Flask side); you'd have to set the AJAX content type to application/json and use JSON.stringify() to actually submit JSON:

var request = $.ajax({
   url: "/json_submit",
   type: "POST",
   contentType: "application/json",
   data: JSON.stringify({
     id: id, 
     known: is_known
   }),  
})  
  .done( function (request) {
})

and now the data can be accessed as a Python structure with the request.get_json() method.

The dataType: "json", parameter to $.ajax is only needed when your view returns JSON (e.g. you used flask.json.jsonify() to produce a JSON response). It lets jQuery know how to process the response.

Solution 2

Can you try like this

var request = $.ajax({
    url: "/json_submit",
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify({
      id: id, 
      known: is_known
    }),  
    dataType: "json",
  })  
   .done( function (request) {
 })

Before that, In your code returns dict object. That is not correct. It returns json like

@app.route("/json_submit", methods=["POST"])
def submit_handler():
    # a = request.get_json(force=True)
    app.logger.log("json_submit")
    return flask.jsonify({'msg': 'success'})
Share:
10,627

Related videos on Youtube

wirrbel
Author by

wirrbel

data scientist and coder, interested in machine learning, PLs and all ways of abstraction.

Updated on June 06, 2022

Comments

  • wirrbel
    wirrbel almost 2 years

    I am writing a small flask based site and I would like to send data from the client to the server using Ajax. Until now I have only used Ajax requests to retrieve data from the server. This time I would like to submit data via POST request.

    This is the receiver on the flask side, I reduced it to barely log a message to avoid any unnecessary errors within the implementation of this route:

    @app.route("/json_submit", methods=["POST"])
    def submit_handler():
        # a = request.get_json(force=True)
        app.logger.log("json_submit")
        return {}
    

    When submitting the ajax request, flask gives me a 400 error

    127.0.0.1 - - [03/Apr/2014 09:18:50] "POST /json_submit HTTP/1.1" 400 -
    

    I can also see this in the web developer console in the browser

    Why is flask not calling submit_handler with the supplied data in the request?

     var request = $.ajax({
        url: "/json_submit",
        type: "POST",
        data: {
          id: id, 
          known: is_known
        },  
        dataType: "json",
      })  
       .done( function (request) {
      })
    
  • sofly
    sofly almost 9 years
    Holy moly, thank you for the first sentence in this answer. I had exempt one view (the main form) with @csrf.exempt but had forgotten to add that same decorator to my actual upload view, which was being called via AJAX. Reading that first sentence somehow set off the lightbulb :)
  • pierrelb
    pierrelb about 7 years
    Hello, you actually do not need to set csrf to a meta tag. Just set the variable like this in your javascript : var csrftoken = "{{ csrf_token() }}"; And by the way do not exempt the token of your route! Otherwise csrf protection is useless... Hope it helps. If needed I can edit an answer.
  • Martijn Pieters
    Martijn Pieters about 7 years
    @pierrelb: I generally use static files for JS code (so it can be cached in the browser long term), and then using a meta tag or data attribute in the dynamic page is a far more scalable option. The answer does include the phrase or in generated JavaScript. And I also clearly state that exempting is an alternative to setting the token in a X-CSRFToken HTTP header.
  • Martijn Pieters
    Martijn Pieters about 7 years
    @pierrelb: in other words: there is no need to edit, those options are already covered.
  • deesolie
    deesolie over 3 years
    Thank you a million times for this answer. Was banging my head against the wall for hours. The ajax set up with the CSRF token is clutch. Why do we need the if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)?
  • Martijn Pieters
    Martijn Pieters over 3 years
    @deesolie: CSRF protection doesn't apply to GET, HEAD, OPTIONS or TRACE requests, so that test avoids adding the header to those requests. See it as a bandwidth-saving measure. You could invert the test, and match your Flask-WTF configuration WTF_CSRF_METHODS option.