7

When I attempt to redirect incoming traffic to https I get an infinite redirect loop.

@app.route('/checkout/')                                                                                                                                                                                        
def checkout():                                                                                                                                                                                                 
    checkout = "https://myapp.herokuapp.com/checkout/"                                                                                                                                              
    if checkout != request.url:                                                                                                                                                                             
        print checkout, request.url                                                                                                                                                                             
        return redirect(checkout)                                                                                                                                                                               
    return render_template('checkout.html', key=keys['publishable_key']) 

The request.url is never changed to prefix https. I want to use heroku's piggyback ssl to minimize cost.

Paco
  • 4,520
  • 3
  • 29
  • 53
The Internet
  • 7,959
  • 10
  • 54
  • 89

7 Answers7

15

1) Do "pip install flask-sslify"

(github is here: https://github.com/kennethreitz/flask-sslify)

2) Include the following lines:

from flask_sslify import SSLify
if 'DYNO' in os.environ: # only trigger SSLify if the app is running on Heroku
    sslify = SSLify(app)
Ryan Shea
  • 4,252
  • 4
  • 32
  • 32
7

On Heroku, SSL (https) is terminated before it reaches your application, so you app never actually sees SSL traffic. To check whether a request was made with https, you instead have to inspect the x-forwarded-proto header. More info here: How to make python on Heroku https only?

UPDATE: For your use, you should just check request.url for "myapp.herokuapp.com/checkout/"; and verify that the header is "https"

Community
  • 1
  • 1
friism
  • 19,068
  • 5
  • 80
  • 116
  • Thanks friism. I have seen the flask-sslify package. Which would meet my needs except I'm using a custom domain along with heroku's subdomain. I only want to put SSL on one page. Thanks for the doc though, I will read through it. – The Internet Feb 27 '13 at 16:15
  • You should just check `request.url` for "http://myapp.herokuapp.com/checkout/" and verify that the header is "https" – friism Feb 27 '13 at 16:53
  • 1
    Thanks, that's what did it. – The Internet Feb 27 '13 at 17:26
  • I've added the comment in the answer - please consider accepting the answer. – friism Feb 27 '13 at 17:46
  • I will heavily consider :) If you mention flask-sslify in your answer I will consider even more heavily. – The Internet Feb 27 '13 at 18:26
6

I tried SSLify, url_for _scheme, and setting a PREFERRED_URL_SCHEME; however none worked out, at the release level at least.. (worked fine locally) Then I thought;

@app.before_request
def beforeRequest():
    if not request.url.startswith('https'):
        return redirect(request.url.replace('http', 'https', 1))

This is essentially another way to get it done without any configurations, or extensions.

Miles
  • 1,104
  • 11
  • 13
  • 1
    This resulted in "This webpage has a redirect loop" for me – tdc Nov 03 '15 at 11:52
  • @tdc Interesting, did you try the answer I gave before someone edited this answer? I haven't tried this, although it seems it should work the same; I know the original definitely worked fine. – Miles Nov 04 '15 at 11:43
  • I never saw the original! SSLify worked for me in the end – tdc Nov 04 '15 at 11:44
0

I was able to repurpose the flask-sslify code for a single view. Just needed to check whether or not the request was being made with SSL and add proper headers to the response. https://github.com/kennethreitz/flask-sslify

@app.route('/checkout/')                                                                                                                                                                                        
def checkout():                                                                                                                                                                                                 
    checkout = "https://myapp.herokuapp.com/checkout/"                                                                                                                                              
    if request.headers.get('X-Forwarded-Proto', 'http') == 'https':                                                                                                                                             
        resp = make_response(render_template('checkout.html', key=keys['publishable_key']))                                                                                                              
        return set_hsts_header(resp)                                                                                                                                                                            
    return redirect(checkout, code=302)                                                                                                                                                                         

def set_hsts_header(response):                                                                                                                                                                                  
    """Adds HSTS header to each response."""                                                                                                                                                                    
    response.headers.setdefault('Strict-Transport-Security', hsts_header)                                                                                                                                       
    return response                                                                                                                                                                                             

def hsts_header():                                                                                                                                                                                              
    """Returns the proper HSTS policy."""                                                                                                                                                                       
    hsts_policy = 'max-age={0}'.format(31536000) #year in seconds                                                                                                                                               
    if self.hsts_include_subdomains:                                                                                                                                                                            
        hsts_policy += '; includeSubDomains'                                                                                                                                                                    
        return hsts_policy 
The Internet
  • 7,959
  • 10
  • 54
  • 89
0

You just need to check the X-Forwarded-Proto header. If its false, redirect to the equivalent https url.

Here the code to enforce https for all calls on a flask app running on heroku:

@app.before_request
def enforceHttpsInHeroku():
  if request.headers.get('X-Forwarded-Proto') == 'http':
  url = request.url.replace('http://', 'https://', 1)
  code = 301
  return redirect(url, code=code)
0

You can do something like this:

@app.before_request
def before_request():
    if 'DYNO' in os.environ: # Only runs when on heroku
        if request.url.startswith('http://'):
            url = request.url.replace('http://', 'https://', 1)
            code = 301
            return redirect(url, code=code)
KetZoomer
  • 2,701
  • 3
  • 15
  • 43
0

On my answer to another question I have stated the upto date Flask recommendations. Use Talisman instead of SSLify.

For Flask use Talisman. Flask, Heroku and SSLify documentations favor the use of Talisman over SSLify because the later is no longer maintained.

From SSLify:

The extension is no longer maintained, prefer using Flask-Talisman as it is encouraged by the Flask Security Guide.

Install via pip:

$ pip install flask-talisman

Instatiate the extension (example):

from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
if 'DYNO' in os.environ:
    Talisman(app)

Talisman enables CSP (Content Security Policy) by default only allowing resources from the same domain to be loaded. If you want to disable it and deal with the implications:

Talisman(app, content_security_policy=None)

If you don't want to disable it you have set the content_security_policy argument to allow resources from external domains, like CDNs, for instance. For that refer to the documentation.

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29