python flask redirect to https from http
Asked Answered
A

17

45

I have a website build using python3.4 and flask...I have generated my own self-signed certificate and I am currently testing my website through localhost.

I am using the python ssl module along with this flask extension: https://github.com/kennethreitz/flask-sslify

context = ('my-cert.pem', 'my-key.pem')
app = Flask(__name__)
sslify = SSLify(app)

...

if __name__ == '__main__':
    app.debug = False
    app.run(
    host="127.0.0.1",
    port=int("5000"),
    ssl_context=context
)

This does not seem to be working however. I took a look in the sslify source code and this line does not seem to be working

def init_app(self, app):
    """Configures the configured Flask app to enforce SSL."""
    app.before_request(self.redirect_to_ssl)
    app.after_request(self.set_hsts_header)

Specifically the function call to redirect_to_ssl (I added my own print statement under the redirect_to_ssl function and my statement was never printed)

def redirect_to_ssl(self):
    print("THIS IS WORKING")
    """Redirect incoming requests to HTTPS."""
    Should we redirect?
    criteria = [
        request.is_secure,
        current_app.debug,
        request.headers.get('X-Forwarded-Proto', 'http') == 'https'
    ]

    if not any(criteria) and not self.skip:
        if request.url.startswith('http://'):
            url = request.url.replace('http://', 'https://', 1)
            code = 302
            if self.permanent:
                code = 301
            r = redirect(url, code=code)
            return r

I am pretty new to python. Any ideas?

Auklet answered 26/8, 2015 at 21:57 Comment(0)
B
59

To me, it appears you're making it more complicated than it needs to be. Here is the code I use in my views.py script to force user to HTTPS connections:

@app.before_request
def before_request():
    if not request.is_secure:
        url = request.url.replace('http://', 'https://', 1)
        code = 301
        return redirect(url, code=code)
Brain answered 26/8, 2015 at 23:3 Comment(7)
I'm not sure how it would redirect more than once, unless you have something else that's redirecting from https back to http somewhere, creating a loop. It should only redirect once.Brain
Reason that @HimanshuMishra ran into issues that they are probably terminating SSL. Checking request.is_secure() is what you want because it respects the X-Forwarded-Protocol header. code.djangoproject.com/ticket/14597Stinkpot
I think the change is as simple as: if not request.is_secure(): as @tuned mentions below.Stinkpot
Should actually be request.is_secureStinkpot
I did try this but if my app is running on port 443 (https), why would the flask app be listening at port 80 (http)?Misquotation
@CamK I thought the same thing before, but now started using cloudfoundry and it is running a load balancer in front of my app (listening on HTTP and HTTPS), so I can't use my own SSL cert anymore since the balancer forwards all traffic to my app on HTTPNomadize
I ended up setting request.scheme = 'https'. This way request.url request.base_url etc. will each be updated.Cade
N
19

According with the docs, after pip install Flask-SSLify you only need to insert the following code:

from flask import Flask
from flask_sslify import SSLify

app = Flask(__name__)
sslify = SSLify(app)

I have done it and it works very well. Am I missing something in the discussion ?

Nonappearance answered 26/4, 2018 at 11:29 Comment(2)
I believe flask_sslify is no longer maintained .. see @Eyal Levin 's answer belowRemmer
Is "app.run" still required and do you specify a port?Misquotation
B
19

The Flask Security Guide recommends using Flask-Talisman.

$ pip install flask-talisman

Usage example:

from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app)

It forces HTTPS by default (from the README):

force_https, default True, forces all non-debug connects to https.


Personally, I got some errors relating to CSP (Content Security Policy) which I disabled with:

Talisman(app, content_security_policy=None)

But use this at your own risk :)

Bowse answered 30/7, 2019 at 11:26 Comment(2)
Also added content_security_policy =None. I tried GOOGLE_CSP_POLICY but it didn't work with google analytics. Also my "Twitter follow me" button wasn't working either. I didn't bother investigating more for something that was working as is beforeChristmann
What are the risks of using talisman?Bagging
E
15

Thanks to answer from Kelly Keller-Heikkila and comment by jaysqrd I ended up doing this in my Flask app:

from flask import request, redirect
...

@app.before_request
def before_request():
    if app.env == "development":
        return
    if request.is_secure:
        return

    url = request.url.replace("http://", "https://", 1)
    code = 301
    return redirect(url, code=code)

I tried the flask_sslify solution suggested by Rodolfo Alvarez but ran into this issue and went with the above solution instead.

If the app is running in development mode or the request is already on https there's no need to redirect.

Eradicate answered 27/11, 2018 at 13:41 Comment(1)
I ended up setting request.scheme = 'https'. This way request.url request.base_url etc. will each be updated.Cade
M
11

Here is a flask solution if you are on aws and behind a load balancer. Place it in your views.py

@app.before_request
def before_request():
    scheme = request.headers.get('X-Forwarded-Proto')
    if scheme and scheme == 'http' and request.url.startswith('http://'):
        url = request.url.replace('http://', 'https://', 1)
        code = 301
        return redirect(url, code=code)
Milker answered 24/11, 2019 at 23:58 Comment(3)
this works for gcp app engine in flex env as well. Great job @edWAstroid
worked for me in cloudfoundryNomadize
If your request is already setting X-Forwarded-Proto, probably better to go with the official ProxyFix werkzeug.palletsprojects.com/en/2.2.x/middleware/proxy_fix like @mdubez's answer.Cade
O
5

The standard solution is to wrap the request with an enforce_ssl decorator that after checking some flags in the app configuration (flags you can set depending on your debugging needs) modifies the request's url with request.url.

As it is written here.

You can modify the code to make it working with before_request as suggested by @kelly-keller-heikkila

Orthopterous answered 26/4, 2016 at 9:22 Comment(0)
Y
5

I use a simple extra app that runs on port 80 and redirect people to https:

from flask import Flask,redirect

app = Flask(__name__)

@app.route('/')
def hello():
    return redirect("https://example.com", code=302)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)
Yacano answered 2/10, 2019 at 21:51 Comment(0)
C
3

An alternative to the other answers that I've been able to use with great success:

from http import HTTPStatus
from typing import Optional

from flask import Response, redirect, request, url_for


def https_redirect() -> Optional[Response]:
    if request.scheme == 'http':
        return redirect(url_for(request.endpoint,
                                _scheme='https',
                                _external=True),
                        HTTPStatus.PERMANENT_REDIRECT)


# ..

if app.env == 'production':
    app.before_request(https_redirect)

# ..
Cleavland answered 16/1, 2020 at 14:4 Comment(0)
O
2

On app engine flex, add:

from werkzeug.middleware.proxy_fix import ProxyFix

def create_app(config=None):

    app = Flask(__name__)
    app.wsgi_app = ProxyFix(app.wsgi_app)

In addition to the solution of:

@app.before_request
def before_request():
    if not request.is_secure:
        url = request.url.replace('http://', 'https://', 1)
        code = 301
        return redirect(url, code=code)

Otherwise it'll cause infinite redirects since SSL is unwrapped behind the proxy but is noted in the headers.

Oblong answered 4/8, 2021 at 23:24 Comment(2)
This should be marked as the correct answer. Using it with the following flags does not seem to need the before request patch: app = Flask(__name__); app.wsgi_app = ProxyFix(app.wsgi_app, x_host=1, x_proto=1)Brok
Just these lines - app = Flask(name); app.wsgi_app = ProxyFix(app.wsgi_app, x_host=1, x_proto=1) fixed the issue of 'Invalid Redirect URI' for me. App is behind AWS load balancer. Definitely this should be the accepted answer for the OP.Foley
R
1

I ran into the same solution running a Flask application in AWS Elastic Beanstalk behind a load balancer. The following AWS docs provided two steps to configure the environment for http redirects: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/configuring-https-httpredirect.html Following both steps fixed my issue.

One thing to note is that you'll have to create the .ebextenions folder at the root level of your application source bundle and add the config file to that .ebextensions folder. The readme here: https://github.com/awsdocs/elastic-beanstalk-samples explains this in a bit more detail.

Rolph answered 2/12, 2019 at 20:43 Comment(0)
C
1

For some reason it seems, requests from a Private AWS API Gateway with a VPC endpoint don't include the "X-Forwarded-Proto" header. This can break some of the other solutions (either it doesn't work or it continuously redirects to the same url). The following middleware forces https on most flask generated internal redirects:

class ForceHttpsRedirects:
    def __init__(self, app):
        self.app = app
    
    def __call__(self, environ, start_response):
        environ["wsgi.url_scheme"] = "https"
        return self.app(environ, start_response)

# Usage
app = flask.Flask(__name__)
app.wsgi_app = ForceHttpsRedirects(app.wsgi_app) # Add middleware to force all redirects to https
Crampon answered 14/12, 2020 at 21:28 Comment(0)
S
1

Use:

app.run(port="443")

All modern browsers automatically use HTTPS when the port is 443 or 8443.

Superheat answered 2/4, 2022 at 5:14 Comment(0)
C
0

I'm using cloud foundry python app which is behind a load balancer (like https://stackoverflow.com/users/5270172/kelly-keller-heikkila said) . This resolution helped me by adding (_external and _Scheme to the url_for function). https://github.com/pallets/flask/issues/773

Carrel answered 28/11, 2018 at 2:38 Comment(0)
A
0

I had the same issue and mine is a brute-force solution, but it works. Heroku in the past suggested flask_sslify, which is not maintained anymore. Nowadays the proper way in Flask should be flask-talisman, but I tried it and it has bad interactions with boostrap templates. I tried the anulaibar solution but it did not always worked for me. The following is what I came up with:

@app.before_request
def before_request():
    # If the request is sicure it should already be https, so no need to redirect
    if not request.is_secure:
        currentUrl = request.url
        if currentUrl.startswith('http://'):
            # http://example.com -> https://example.com
            # http://www.example.com -> https://www.example.com
            redirectUrl = currentUrl.replace('http://', 'https://', 1)
        elif currentUrl.startswith('www'):
            # Here we redirect the case in which the user access the site without typing any http or https
            # www.example.com -> https://www.example.com
            redirectUrl = currentUrl.replace('www', 'https://www', 1)
        else:
            # I do not now when this may happen, just for safety
            redirectUrl = 'https://www.example.com'
        code = 301
        return redirect(redirectUrl, code=code)

I have the domain registered in godaddy which is also redirecting to https://www.example.com.

Archicarp answered 10/4, 2021 at 7:30 Comment(0)
G
0

In my case Flask app is sitting behind AWS API Gateway and solutions with @app.before_request were giving me permanent redirects.

The following simple solution finally worked:

@app.after_request
def adjust_response(response):
    ....
    if response.location:
        if app.env != "development":
            response.location = response.location.replace("http://", "https://", 1)
    return response
Gobetween answered 31/10, 2021 at 3:9 Comment(1)
This is fixing the symptom, but not the underlying problem. You need to set up Flask to correctly handle headers in the proxy environment for security reasons. Doing it properly is actually less code anyway.Bluetongue
G
0

When you expose the ports the flask in the logs shows what port it is running on?

it cannot serve http and https without their being a listener on both ports.

just run a container that redirects to the the https url serving on port 80 or 8080 depending on access. This is what brought me to this page to see if anyone else wanted to fix such a flask issue.

Grooved answered 24/8, 2023 at 13:36 Comment(0)
P
0

My Flask app was sitting on an AWS EC2, behind an ELB, inside a VPC, and on a non-standard port (8000) without a redirect at 80 to 443, so it kept missing the Access Control Headers, and also setting the response location to http, with 301 permanent redirects. A combination of @Altair7852's solution and some manual addition of response headers did the trick:

@app.after_request
def after_request(response):
    if response.location and app.env != "development":
        response.location = response.location.replace("http://", "https://", 1)
    response.headers.add('Access-Control-Allow-Origin', '{mysiteurl}')
    return response

Of course, if security is not that particular of an issue it is also reasonable to set allow origin header to '*' for no hassle. I did try to use @Nick K9's suggestion with werkzeug, but I can't get it working correctly, probably again because of the setup I'm in.

Still, it's a quick bandaid fix to just use the after request modifier function.

Passant answered 3/5 at 23:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.