Securing Django on Heroku and Secret Key Generation

Heroku is an easy platform to host a Django app on but you should set a few things to make sure your app is secure. Here’s a quick way to do this.

This guide is for Django 3.2.11 (on Python 3.10.2) but it should work fine on 4.0.1 too if you’re not into LTS. It also assumes that you are using Heroku PostgreSQL if you are using a DB.

The easiest way to get your app working is by adding Django-Heroku to it.

This will automatically configure DATABASE_URL, ALLOWED_HOSTS…

Also,

If you set the SECRET_KEY environment variable, it will automatically be used in your Django settings

We can use this feature to our advantage to have an app that works well both in development and securely in production with minimal changes.

Add something like the following to your settings.py. Change the values if they are not suitable for your app.

if os.environ.get("SECRET_KEY", "") != "":
    # HTTP Strict Transport Security - careful now
    SECURE_HSTS_SECONDS = os.environ.get("SECURE_HSTS_SECONDS", 0)
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True

    # HTTPS redirects
    SECURE_SSL_REDIRECT = True
    SECURE_PROXY_SSL_HEADER = (
        "HTTP_X_FORWARDED_PROTO",  # Heroku strips and sets X-Forwarded-Proto correctly
        "https",
    )

    # Secure cookies
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True

    # Only run in debug mode if local
    DEBUG = False

This enables security settings if SECRET_KEY is specified. The app won’t start if it is actually an empty string and the insecure prefixed one from the default template will show a warning when we run the deployment check. SECURE_HSTS_SECONDS is set separately as this can cause problems if set incorrectly and a value of 0 also disables the other HSTS flags.

Now we need to generate a secret key for production. This easiest way to do this is the same way Django does. Open a Django shell:

./manage.py shell

Then run the following:

from django.core.management import utils
utils.get_random_secret_key()

This is equivalent to the following:

from django.utils import crypto
crypto.get_random_string(50, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)")

The first param (length) is required from Django 4.0 onwards.

If you don’t want to use a Django shell or you are using an old version then you can go a level deeper and open a python shell:

python3

Then run:

import secrets
secrets.token_urlsafe(100)

Then open the web interface for your app and go to Settings > Config Vars. Click the “Reveal Config Vars” button and add SECRET_KEY with the value you generated.

Heroku Settings Reveal Config Vars

Set SECURE_HSTS_SECONDS to 3600 to start and increase later. Leave DATABASE_URL and other settings as-is.

Then run a deployment check and make sure that there are no warnings:

heroku run -a <YOUR APP NAME> ./manage.py check --deploy

It is also worth checking that you are running the latest patch versions of Django and Python.

heroku run -a <YOUR APP NAME> ./manage.py --version
heroku run -a <YOUR APP NAME> python --version

If your requirements.txt looks something like the following then Django should auto-update every time you deploy.

django>=3.2.10,<3.3
psycopg2
django-heroku
gunicorn

To update Python you may need to change your runtime.txt file:

python-3.10.2

If you need to perform any actions before a deployment then you can do this with an entry at the top of your Procfile. For example, to automatically run any Django database migrations you could put this as the first line.

release: python manage.py migrate

Other things to consider are setting up Multi-Factor Authentication. I like to use the andOTP - Android OTP Authenticator app.

You can also make sure your SSH Keys are in shape. I prefer elliptic-curve (EC) e.g. ed25519 to RSA.


This blog is treeware! If you found it useful then please plant a tree.
Donate a treeDonate a tree🌳🌳