Software Engineering

Django App Security: A Pydantic Tutorial, Part 4


This is the fourth installment in a series on leveraging pydantic for Django-based projects. Before we continue, let’s review: In the series’ first installment, we focused on pydantic’s use of Python type hints to streamline Django settings management. In the second tutorial, we used Docker while building a web application based on this concept, aligning our development and production environments. The third article described hosting our app on Heroku.

Written with a security-first design principle—a departure from Python libraries such as Flask and FastAPI—Django features baked-in support for identifying many common security pitfalls. Using a functional web application example, running and available to the internet, we will leverage Django to enhance application security.

To follow along, please be sure to first deploy our example web application, as described in the first installment of this tutorial series. We will then assess, fortify, and verify our Django app’s security, resulting in a site that strictly supports HTTPS.

Step 1: Evaluate Application Vulnerabilities

One way to perform Django’s security check and site verification sequence is to navigate to our application’s root directory and run:

python manage.py check --deploy --fail-level WARNING

But this command is already contained in our app’s heroku-release.sh file (per the steps taken in part 3 of this tutorial series), and the script automatically runs when the application is deployed.

The check command in the preceding script generates a list of Django security-related warnings, viewable by clicking the Show Release Log button in Heroku’s dashboard. The output for our application is as follows:

System check identified some issues:
​
WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting. If your entire site is served only over SSL, you may want to consider setting a value and enabling HTTP Strict Transport Security. Be sure to read the documentation first; enabling HSTS carelessly can cause serious, irreversible problems.
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True. Unless your site should be available over both SSL and non-SSL connections, you may want to either set this setting True or configure a load balancer or reverse-proxy server to redirect all connections to HTTPS.
?: (security.W012) SESSION_COOKIE_SECURE is not set to True. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions.
?: (security.W016) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. Using a secure-only CSRF cookie makes it more difficult for network traffic sniffers to steal the CSRF token.​
System check identified 4 issues (0 silenced).

Reinterpreted, the preceding list suggests we address the following four security concerns:

Item

Value (Requirement: Set to True)

Outcome

HSTS

SECURE_HSTS_SECONDS

Enables HTTP Strict Transport Security.

HTTPS

SECURE_SSL_REDIRECT

Redirects all connections to HTTPS.

Session Cookie

SESSION_COOKIE_SECURE

Impedes user session hijacking.

CSRF Cookie

CSRF_COOKIE_SECURE

Hinders theft of the CSRF token.

We will now address each of the four issues identified. Our HSTS setup will account for the (security.W004) warning’s message about enabling HSTS carelessly to avoid major site breakage.

​Step 2: Bolster Django Application Security

Before we address security concerns pertaining to HTTPS, a version of HTTP that uses the SSL protocol, we must first enable HTTPS by configuring our web app to accept SSL requests.

To support SSL requests, we will set up the configuration variable USE_SSL. Setting up this variable will not change our app’s behavior, but it is the first step toward additional configuration modifications.

Let’s navigate to the Heroku dashboard’s Config Vars section of the Settings tab, where we can view our configured key-value pairs:

Key

Value

ALLOWED_HOSTS

[“hello-visitor.herokuapp.com”]

SECRET_KEY

Use the generated key value

DEBUG

False

DEBUG_TEMPLATES

False

By convention, Django security settings are stored within a web app’s settings.py file. settings.py includes the SettingsFromEnvironment class that is responsible for environment variables. Let’s add a new configuration variable, setting its key to USE_SSL and its value to TRUE. SettingsFromEnvironment will respond and handle this variable.

While in our settings.py file, let’s also update the HTTPS, session cookie, and CSRF cookie variable values. We will wait to enable HSTS, as this requires an additional step.

The key edits to support SSL and update these three existing variables are:

class SettingsFromEnvironment(BaseSettings):
    USE_SSL: bool = False
​
try:
   # ...
    USE_SSL = config.USE_SSL

# ...
if not USE_SSL:
    SECURE_PROXY_SSL_HEADER = None
    SECURE_SSL_REDIRECT = False
    SESSION_COOKIE_SECURE = False
    CSRF_COOKIE_SECURE = False
else:
    # (security.W008)
    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
    SECURE_SSL_REDIRECT = True
    # (security.W012)
    SESSION_COOKIE_SECURE = True
    # (security.W016)
    CSRF_COOKIE_SECURE = True

These Django security updates are important for the protection of our application. Each Django setting is labeled with its corresponding security warning identifier as a code comment.

The SECURE_PROXY_SSL_HEADER and SECURE_SSL_REDIRECT settings ensure our application only supports connection to our site via HTTPS, a far more secure option than unencrypted HTTP. Our modifications will ensure that a browser trying to connect to our site via HTTP is redirected to connect via HTTPS.

To support HTTPS, we need to provide an SSL certificate. Heroku’s Automated Certificate Management (ACM) feature fits the bill, and is set up by default for Basic or Professional dynos.

With these settings added to the settings.py file, we can push our code changes, navigate to Heroku’s admin panel, and trigger another application deployment from the repo to manifest these changes on our site.

Step 3: Verify HTTPS Redirection

After deployment completes, let’s check the HTTPS functionalities on our site and confirm that the site:

  • Is directly accessible using the https:// prefix.
  • Redirects from HTTP to HTTPS when using the http:// prefix.

With HTTPS redirection working, we have addressed three of our four initial warnings (nos. 2, 3, and 4). Our remaining concern to address is HSTS.

Step 4: Enforce HSTS Policy

HTTP Strict Transport Security (HSTS) restricts compatible browsers to only using HTTPS to connect to our site. The very first time our site is accessed via a compatible browser and over HTTPS, HSTS will return a Strict-Transport-Security header response that prevents HTTP access from that point forward.

In contrast with standard HTTPS redirection that is page-specific, HSTS redirection applies to an entire domain. In other words, without HSTS support, a thousand-page website could potentially be burdened with a thousand unique requests for HTTPS redirection.

Additionally, HSTS uses its own, separate cache that will remain intact, even when a user clears their “regular” cache.

To implement HSTS support, let’s update our app’s settings.py file:

 if not USE_SSL:
     SECURE_PROXY_SSL_HEADER = None
     SECURE_SSL_REDIRECT = False
     SESSION_COOKIE_SECURE = False
     CSRF_COOKIE_SECURE = False
+    SECURE_HSTS_INCLUDE_SUBDOMAINS = False
+    SECURE_HSTS_PRELOAD = False

Then skip down to the bottom of the else block just after that and add these lines:

   # IMPORTANT:
   # (-) Add these only once the HTTPS redirect is confirmed to work
   #
   # (security.W004)
   SECURE_HSTS_SECONDS = 3600  # 1 hour
   SECURE_HSTS_INCLUDE_SUBDOMAINS = True
   SECURE_HSTS_PRELOAD = True

We have updated three settings to enable HSTS, as recommended by Django documentation, and chosen to submit our site to the browser preload list. You may recall that our (security.W004) warned against carelessly enabling HSTS. To avoid any mishaps related to prematurely enabled HSTS, we set the value for SECURE_HSTS_SECONDS to one hour; this is the amount of time your site would be broken if set up improperly. We will test HSTS with this smaller value to confirm that the server configuration is compatible before we increase it—a common option is 31536000 seconds, or one year.

Now that we have implemented all four security steps, our site is armed with HTTPS redirect logic combined with an HSTS header, thus ensuring that connections are supported by the added security of SSL.

An added benefit of coding our settings logic around the USE_SSL configuration variable is that a single instance of code (the settings.py file) works on both our development system and our production servers.

Django Security for Peace of Mind

Safeguarding a site is no easy feat, but Django makes it possible with a few simple, yet crucial, steps. The Django development platform empowers you to protect a site with relative ease, irrespective of whether you are a security expert or a novice. I have successfully deployed countless Django applications to Heroku and I sleep well at night—as do my clients.


The Toptal Engineering Blog extends its gratitude to Stephen Harris Davidson for reviewing and beta testing the code samples presented in this article.