How to make python on Heroku https only?
I have python/django app on Heroku (Cedar stack) and would like to make it accessible over https only. I have enabled the "ssl piggyback"-option, and can connect to it via https.
But what is the best way to disable http access, or redirect to https?
Solution 1:
Combining the answer from @CraigKerstiens and @allanlei into something I have tested, and verified to work. Heroku sets the HTTP_X_FORWARDED_PROTO to https when request is ssl, and we can use this to check:
from django.conf import settings
from django.http import HttpResponseRedirect
class SSLMiddleware(object):
def process_request(self, request):
if not any([settings.DEBUG, request.is_secure(), request.META.get("HTTP_X_FORWARDED_PROTO", "") == 'https']):
url = request.build_absolute_uri(request.get_full_path())
secure_url = url.replace("http://", "https://")
return HttpResponseRedirect(secure_url)
Solution 2:
Django 1.8 will have core support for non-HTTPS redirect (integrated from django-secure):
SECURE_SSL_REDIRECT = True # [1]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
In order for SECURE_SSL_REDIRECT
to be handled you have to use the SecurityMiddleware
:
MIDDLEWARE = [
...
'django.middleware.security.SecurityMiddleware',
]
[1] https://docs.djangoproject.com/en/1.8/ref/settings/#secure-ssl-redirect
Solution 3:
Not sure if @CraigKerstiens's answer takes into account that request.is_secure()
always returns False
if behind Heroku's reverse proxy and not "fixed". If I remember correctly, this will cause a HTTP redirect loop.
If you are running Django with gunicorn, another way to do it is to add the following to gunicorn's config
secure_scheme_headers = {
'X-FORWARDED-PROTO': 'https'
}
Run with some like this in your Procfile
web: python manage.py run_gunicorn -b 0.0.0.0:$PORT -c config/gunicorn.conf
By setting gunicorn's secure-scheme-header
, request.is_secure()
will properly return True
on https requests. See Gunicorn Config.
Now @CraigKerstiens's middleware will work properly, including any calls to request.is_secure()
in your app.
Note: Django also has the same config setting call SECURE_PROXY_SSL_HEADER
, buts in the dev version.
Solution 4:
What framework are you using for your application? If you're using Django you could simple use some middleware similar to:
import re
from django.conf import settings
from django.core import urlresolvers
from django.http import HttpResponse, HttpResponseRedirect
class SSLMiddleware(object):
def process_request(self, request):
if not any([settings.DEBUG, request.is_secure()]):
url = request.build_absolute_uri(request.get_full_path())
secure_url = url.replace("http://", "https://")
return HttpResponseRedirect(secure_url)
Solution 5:
2020 update:
If you are using Flask, I would recommend the following:
@app.before_request
def before_request():
if 'DYNO' in os.environ:
if request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
The above works excellent on Heroku and allows you to use http
in local development with heroku local
.
Flask-SSLify is no longer maintained and no longer officially supported by the Flask community.
2014 original answer:
If you're using Flask, this works quite well:
- Do "pip install flask-sslify"
(github is here: https://github.com/kennethreitz/flask-sslify)
- 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)