These docs are a work-in-progress and are currently undergoing a major rewrite!

Security

We can't guarantee that your site will be 100% secure (it is your code in the end), but we can help you take some basic steps in the right direction.

If you haven't read it before, we recommend you read Django's page on security. There are a lot of things that Django does for you, and more that Forge can enable for you by default.

Django settings

In production, these settings will be configured automatically by Forge:

  • CSRF_TRUSTED_ORIGINS
  • SECURE_SSL_REDIRECT
  • SECURE_PROXY_SSL_HEADER
  • SESSION_COOKIE_SECURE
  • CSRF_COOKIE_SECURE
  • SECURE_HSTS_SECONDS
  • SECURE_HSTS_INCLUDE_SUBDOMAINS
  • SECURE_HSTS_PRELOAD
  • SECURE_CONTENT_TYPE_NOSNIFF
  • SECURE_BROWSER_XSS_FILTER
  • SECURE_REFERRER_POLICY
  • X_FRAME_OPTIONS

Of course you can tweak them in settings.py as needed.

Require login

If most pages in your app require users to be logged in, don't forget to make it a requirement on the view!

One of the reasons we like class-based views is that it makes it easier to share view logic across your app. When we start projects, we often make a base view mixin that requires login and we use that throughout the app. Over time there are often other little pieces of logic that get added to this base view too, and are automatically included in the rest of the views.

from django.contrib.auth.mixins import LoginRequiredMixin


class BaseLoggedInViewMixin(LoginRequiredMixin):
    pass
from django.views import generic

from myproject.views import BaseLoggedInViewMixin


class MyView(BaseLoggedInViewMixin, generic.TemplateView):
    template_name = "my_view.html"

Restrict access to objects

A common mistake is to allow users to see objects that belong to other users. For most things in your database, there will be some kind of "owner" (a user or team) and when you have views that display the objects, it's easy to forget to check if the user has permission to see them.

An example solution to this is to filter the get_queryset when using the Django generic class-based views. So if we had a Project model that was owned by a Team, we could limit the instances available to the specific user like this:

class ProjectDetailView(BaseLoggedInViewMixin, ProjectDetailMixin, generic.DetailView):
    model = Project
    slug_field = "uuid"
    slug_url_kwarg = "uuid"

    def get_queryset(self):
        team_ids = self.request.user.teams.values_list("id", flat=True)
        return super().get_queryset().filter(team_id__in=team_ids)

Often times, you'll have multiple views with similar logic like this (detail, update, delete) and can share the get_queryset method with a mixin:

class ProjectDetailMixin:
    model = Project
    slug_field = "uuid"
    slug_url_kwarg = "uuid"

    def get_queryset(self):
        team_ids = self.request.user.teams.values_list("id", flat=True)
        return super().get_queryset().filter(team_id__in=team_ids)


class ProjectDetailView(BaseLoggedInViewMixin, ProjectDetailMixin, generic.DetailView):
    ...

class ProjectUpdateView(BaseLoggedInViewMixin, ProjectDetailMixin, generic.UpdateView):
    ...

If a user tries to view a project they don't have access to, they will get a 404.

Deploy checks

In the Heroku "release" phase we run manage.py check --deploy --fail-level WARNING.

This runs a number of Django system checks (many related to the settings above) and will prevent deploying your app if any checks fail. You can also create your own checks that will run during this process.

Required settings

In settings.py you'll notice that we either use environ["key"] or environ.get("key", default_value). This is the difference between required environment variables and optional environment variables.

So if you have a required environment variable, you can access it like this which will throw a KeyError in the release phase if it hasn't been set:

# Required!
SECRET_KEY = environ["SECRET_KEY"]

# Optional
DEFAULT_REPLYTO_EMAIL = environ.get("DEFAULT_REPLYTO_EMAIL", None)

This is effectively another form of deployment check! When you add your own env-based settings, you should be intentional about which way you load them.

Updating dependencies

Vulnerable dependencies are a common source of problems. Remember, the code you write is only part of your app. The rest of it is written by other people, and pulled in through your dependencies.

Assuming you're on GitHub, you should take advantage of their vulnerability alerts.

The simplest solution for most people is to update to the latest versions whenever possible. That's typically where vulnerabilities get fixed.

To do that we recommend using Deps (a tool of ours) but you can also use GitHub Dependabot or do your best to stay on top of it yourself by running poetry update periodically.