2

I'm using Cloudfront to deliver a custom domain and associated TLS cert for a Flask app hosted on Heroku (free tier). I have a form which POSTs input from the user which is processed by the Flask app. Based on the input and successful form validation, a new template is rendered where further user information is collected (i.e. two step form).

When testing locally or accessing the Heroku app directly (example_app.herokuapp.com) everything works fine. When I test via the custom URL/Cloudfront, the page simply reloads the first form step.

I want to use Cloudfront for custom domain/cert but I'm not too bothered about caching - would be nice to have but I'd rather my app worked correctly!

What I've tried:

1) On Cloudfront Distribution >> Behaviours I've tried to disable all caching and allowed POST:

  • Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE

  • Cache Based on Selected Request Headers: All

  • Object Caching: Customize

  • Minimum TTL: 0

  • Maximum TTL: 0

  • Default TTL: 0

  • Query String Forwarding and Caching: Forward all, cache based on all

2) Within Flask app I've added code to add a no-cache header:

As described here and code below.

@bp.after_request
def add_header(r):
    r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    r.headers["Pragma"] = "no-cache"
    r.headers["Expires"] = "0"
    return r

Here's the section of my Flask Routes file which returns the form page template...

# Route for activation form page
@bp.route('/activate/', methods=['GET', 'POST'])
def activate():
    form = ActivateForm()

    if form.validate_on_submit():
        # Get the details entered by the customer
        email = form.email.data
        value2 = form.value2.data

        # Check that input is valid
        result = check_valid(value2)

        # if valid
        if result['status'] == 1:
            # do stuff to activate subscription and assign sub_id
            # Redirect to sucessful activation page
            return render_template('finish.html', title='Finalising your subscription', sub_id=sub_id, email=email)

    # if the form isn't valid or hasn't been submitted...
    return render_template('activate.html', title='Activate', form=form)

When I test locally or test directly against Heroku (example_app.herokuapp.com) everything works as expected:

  • on initial page visit or if the form content is invalid, the activate.html template loads.

  • on submission of valid form, the instructions within if result['status'] == 1: run and the finish.html template loads.

Note: the page URL does not change between the two form steps/loads.

However, when I try to access the app via Cloudfront:

  • on initial page visit or if the form content is invalid, the activate.html template loads as expected.

  • on submission of valid form, the activate.html template loads again and the instructions within if result['status'] == 1: do not execute.

I've tried disabling caching on both Cloudfront and the Origin app but it still looks like Cloudfront is serving a cached version of the page.

UPDATE: Further testing shows that form.validate_on_submit() and even form.is_submitted() both evaluate to False. This explains the observable action that the page just reloads the form as if it hasn't been submitted yet. I don't understand why I'm seeing this behavior. I've allowed POST on Cloudfront distribution but do I need to set something else on Cloudfront to have it send the form values? Or is it something on the Origin web server (gunicorn 19.9.0) which is dropping the form details?

UPDATE 2: I added Flask_cors to the application and added @cross_origin(allow_headers=['Content-Type']) decorator to test route. Same issue remains. I don't fully understand CORS issues and I had never used Flask_cors previously but hopefully this has ruled CORS related issues out. I'm open to correction on this if someone more experienced feels this is a CORS related issue.

UPDATE 3 (workaround): I replaced form.validate_on_submit() with if request.method == 'POST': and the application appears to be working.

However:

  1. I still don't understand why the form isn't being return correcting. Can anyone explain what might be happening between Cloudfront and the Heroku App/webserver?

  2. Is there any downside to just using if request.method == 'POST':? What's best practice for manually replacing the form validation? Do I need to validate at all (guessing yes but I need to test to see if I can break the app without form.validate_on_submit())?

  3. Why do form.validate_on_submit() and form.is_submitted() return false but I can access all form values via value2 = form.value2.data, etc.?

UPDATE 4: Tagging Flask-WFT as there may be something in is_submitted() which is at fault here.

dduffy
  • 356
  • 3
  • 8
  • Any updates on that? I'm facing the same problem – EduGord Mar 19 '20 at 13:10
  • @EduGord I never figured out what exactly caused the problem. I deleted the Cloudfront config and started from scratch and it started working correctly. I don't know what was different as I put what should have been the same settings in the second time. – dduffy Mar 25 '20 at 10:28

1 Answers1

1

Never figured out what the specific issue was. I deleted and re-created Cloudfront and Flask-WFT is_submitted() started working as expected. So... maybe my original config had a type or was different in some way?

Here's the current Cloudfront config if it helps anyone...

Delivery Method: Web

Cookie Logging: Off

Custom SSL Client Support: Clients that Support Server Name Indication (SNI) - (Recommended)

Security Policy: TLSv1.2_2018

Supported HTTP Versions: HTTP/1.1, HTTP/1.0

Origin Protocol Policy: HTTPS Only

Origin Response Timeout: 30

Origin Kepp-alive Timeout: 5

Viewer Protocol Policy: Redirect HTTP to HTTPS

Forward Query Strings: Yes

dduffy
  • 356
  • 3
  • 8