简体   繁体   中英

Redirect HTTP to HTTPS on Flask+Heroku

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.

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)

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"

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.

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 

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)

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)

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM