How to Set Up report-uri.com on Django
In recent years browsers have gained many powers to report back problems they encounter on your site, such as:
- Network Error Logging (NEL) can report bad HTTP statuses, expired TLS certificates, etc.
- Content Security Policy can report banned resources found on your site.
- Deprecation reports can tell you that you’re using web APIs that will soon be removed.
Browsers send these reports to URIs 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 URLs 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:
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 django.test import RequestFactory, SimpleTestCase
from db_buddy.core.middleware import ReportUriMiddleware
class ReportUriMiddlewareTests(SimpleTestCase):
request_factory = RequestFactory()
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:
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
Read my book Boost Your Git DX to Git better.
One summary email a week, no spam, I pinky promise.
Related posts:
- How to Score A+ for Security Headers on Your Django Website
- Scoring A+ for Security Headers on My Cloudfront-Hosted Static Website
Tags: django