How to Set Up report-uri.com on Django

2021-01-05 Let us see things we could not see before!

In recent years browsers have gained many powers to report back problems they encounter on your site, such as:

Browsers send these reports to URI’s listed in specific security headers, including the exiperimental Report-To header. These are really useful since they can uncover issues that would otherwise go unseen.

A service for collecting, parsing, and making sense of these reports is report-uri.com. It’s run by Scott Helme a security researcher who also made the useful free tool securityheaders.com. It makes a lot of sense to use a separate service for receiving browser reports, since if you have a problem on your own site, it’s likely you’d have problems collecting the reports too!

Yesterday I set up report-uri.com on my new Django project db-buddy.com. Here’s how I did it.

Note: I added the headers from within Django. This makes sense for me since I’m deploying on Heroku and serve all URL’s from Django, including static assets via Whitenoise. If your site is a bit more complicated than this, you might want to add the headers via a wrapping web server, such as nginx, in which case follow the report-uri.com docs.

Adding the Headers

First, I signed up. After the usual account creation I landed on the setup screen. This provides the values to plug into the various headers:

report-uri.com setup screen

Second, I added the Content Security Policy report. I’m using django-csp to control my CSP header, so this required just one more setting:

CSP_REPORT_URI = "https://dbbuddy.report-uri.com/r/d/csp/enforce"

Third, I added a middleware to inject two more headers - the generic Report-To and NEL for network error logging:

class ReportUriMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        # Header values from https://report-uri.com/account/setup/
        response["Report-To"] = '{"group":"default", ...}'
        response["NEL"] = '{"report_to":"default", ...}'
        return response

This fit in just after my other security header middleware:

MIDDLEWARE = [
    ...
    "django.middleware.security.SecurityMiddleware",
    "csp.middleware.CSPMiddleware",
    "django_feature_policy.FeaturePolicyMiddleware",
    "db_buddy.core.middleware.ReportUriMiddleware",
    ...
]

I also added a test, verifying that the middleware worked and I’d copied the JSON from report-uri.com correctly:

import json

from django.http import HttpResponse

from db_buddy.core.middleware import ReportUriMiddleware
from db_buddy.test import SimpleTestCase


class ReportUriMiddlewareTests(SimpleTestCase):
    def test_middleware(self):
        def dummy_view(request):
            return HttpResponse()

        middleware = ReportUriMiddleware(dummy_view)
        request = self.request_factory.get("/")

        response = middleware(request)

        assert json.loads(response["Report-To"])
        assert json.loads(response["NEL"])

Reports Appear

Once the above changes were deployed, reports started coming in. For example here’s a network error report I received from someone testing my /500/ URL, which demoes the “Internal Server Error” screen:

report-uri.com network error reports

Nice!

Fin

It’s awesome to see this development in browsers. report-uri.com is a really easy to set up service and I’m looking forward to using it going forwards.

—Adam


Working on a Django project? Check out my book Speed Up Your Django Tests which covers loads of best practices so you can write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: django