Securing Django Applications: A Pydantic Guide, Part 4

This article is the fourth in a series exploring how to effectively utilize pydantic in Django projects. As a quick recap: The first article demonstrated how pydantic’s type hinting simplifies Django settings configuration. The second article guided readers through building a web app with Docker, using these principles to maintain consistency between development and production environments. In the third article, we deployed this app to Heroku.

Django, designed with a security-first approach, stands apart from libraries like Flask and FastAPI by providing built-in safeguards against common vulnerabilities. In this article, we will enhance the security of a functional web application, already deployed and accessible online, using Django’s built-in features.

To follow along, ensure you have deployed the example web application from the first article in this series. We’ll then assess, strengthen, and verify the Django app’s security, resulting in a site that strictly enforces HTTPS.

Step 1: Assessing Application Security

Navigating to the application’s root directory and executing the following command initiates Django’s security check and site verification process:

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

However, this command is already integrated into our app’s heroku-release.sh file (as part of the steps outlined in part 3 of this series) and is automatically executed upon application deployment.

The check command in the script generates a series of Django security warnings](https://docs.djangoproject.com/en/4.0/ref/checks/), accessible by clicking the Show Release Log button in [Heroku’s dashboard. For our application, the output is:

1
2
3
4
5
6
7
8
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).

This output highlights four security areas that require attention:

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 systematically address each of these four identified concerns. Importantly, our HSTS implementation will take into account the (security.W004) warning regarding potential site breakage from careless HSTS activation.

Step 2: Enhancing Django Application Security

Before we can implement security measures related to HTTPS (HTTP with SSL), we first need to enable HTTPS by configuring our web app to accept SSL requests.

To do so, we’ll set up the USE_SSL configuration variable. While this variable won’t immediately alter the app’s behavior, it’s a crucial first step towards further configuration adjustments.

In the Heroku dashboard, navigate to the Config Vars section under the Settings tab to view the configured key-value pairs:

Key

Value

ALLOWED_HOSTS

["hello-visitor.herokuapp.com"]

SECRET_KEY

Use the generated key value

DEBUG

False

DEBUG_TEMPLATES

False

Django security settings are conventionally stored within a web app’s settings.py file. The settings.py file contains the SettingsFromEnvironment class responsible for handling environment variables. We’ll add a new configuration variable named USE_SSL and set its value to TRUE, which SettingsFromEnvironment will process.

While we are in the settings.py file, let’s also update the existing variables for HTTPS, session cookies, and CSRF cookies. We will enable HSTS later, as this requires an additional step.

Here are the key edits to support SSL and update the three existing variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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 crucial for protecting our application. Each Django setting is labeled with its corresponding security warning identifier in a code comment.

The SECURE_PROXY_SSL_HEADER and SECURE_SSL_REDIRECT settings guarantee that our application can only be accessed via HTTPS, which is significantly more secure than unencrypted HTTP. Our modifications will ensure that any browser attempting to connect to our site using HTTP is redirected to use HTTPS.

To enable HTTPS, we need to obtain an SSL certificate. Fortunately, Heroku’s Automated Certificate Management (ACM) feature provides this functionality and is enabled by default for Basic or Professional dynos.

After adding these settings to the settings.py file, we can push the changes, go to the Heroku admin panel, and trigger another application deployment from the repository to apply these changes to our live site.

Step 3: Verifying HTTPS Redirection

Once deployment is complete, we need to verify that the HTTPS functionalities are working as expected by confirming that the site:

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

Successfully implementing HTTPS redirection addresses three out of the four initial warnings (numbers 2, 3, and 4). The remaining concern is HSTS.

Step 4: Implementing HSTS Policy

HTTP Strict Transport Security (HSTS) is a security mechanism that forces compatible browsers to connect to our site exclusively over HTTPS. Upon the very first HTTPS access to our site from a compatible browser, HSTS sends a Strict-Transport-Security header response, which then prevents any future HTTP access.

Unlike standard, page-specific HTTPS redirection, HSTS redirection applies to the entire domain. This means that, without HSTS, a site with a thousand pages could potentially require a thousand individual HTTPS redirection requests.

Furthermore, HSTS utilizes its own dedicated cache, which persists even when a user clears their regular browser cache.

To implement HSTS support, we’ll make the following updates to our app’s settings.py file:

1
2
3
4
5
6
7
 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

Next, scroll to the bottom of the else block immediately following the code above and add these lines:

1
2
3
4
5
6
7
   # 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 now enabled HSTS by updating three settings, as per the recommendations of Django documentation, and opted to submit our site to the browser preload list. Recall that the (security.W004) warning cautioned against prematurely enabling HSTS. To mitigate any potential issues, we set SECURE_HSTS_SECONDS to one hour. This represents the duration for which your site would be inaccessible if HSTS is set up incorrectly. By testing with a shorter timeframe, we can confirm the server configuration is compatible before increasing it. A common setting is 31536000 seconds, which equates to one year.

With all four security steps implemented, our site now benefits from both HTTPS redirection logic and an HSTS header, ensuring that all connections are secured with SSL.

Another advantage of structuring our settings logic around the USE_SSL configuration variable is that a single code instance (the settings.py file) functions seamlessly across both development and production environments.

Django Security for Peace of Mind

While website security is a complex undertaking, Django simplifies the process with a few straightforward yet essential steps. Whether you are a seasoned security expert or a relative newcomer, the Django platform empowers you to protect your sites with relative ease. I have successfully deployed countless Django applications to Heroku, allowing both myself and my clients to rest easy knowing their applications are secure.


The Toptal Engineering Blog would like to express its sincere gratitude to Stephen Harris Davidson for reviewing and beta testing the code samples provided in this article.

Licensed under CC BY-NC-SA 4.0