How to add a .well-known URL to your Django site
/.well-known/ URL path prefix is a reserved name space for serving particular static files used by other systems that might interact with your site. It was established by RFC 5785 and updated in RFC 8615. The IANA maintains a list of possible files in the Well Known URIs registry, but others are in wide use without official registeration (yet?).
/.well-known/acme-challengeis used for acknowledging HTTPS certificate challenges through Automatic Certificate Management Environment (ACME). This is used by popular free certificate authority Lets Encrypt.
/.well-known/apple-app-site-associationallows links to your domain to open an iOS app.
/.well-known/assetlinks.jsonallows links to your domain to open an app on Android phones.
/.well-known/security.txtis a proposed standard for hosting your site’s security policy that others should use when they find vulnerabilities in your site.
To add such a URL to a Django application, you have a couple of options.
You could serve it from a web server outside your application, such as nginx. The downside of this approach is that if you move your application to a different web server, you’ll need to redo that configuration. Also, you might be tracking your application code in Git, but not your web server configuration, and it’s best to track changes to your
/.well-known/ files, so you don’t lose them.
Instead, it’s better to serve it as a normal URL from within Django. It becomes another view that you can test and update over time.
Let’s look at serving a
/.well-known/security.txt URL, which needs only a plain text response.
You can generate a
security.txt file for you site with the form at securitytxt.org. A minimum file containts only a method of contact for security reports. Let’s use this example one line file:
To serve this from Django, we’d first need to add a new view in our “core” app:
from django.http import HttpResponse from django.views.decorators.http import require_GET @require_GET def security_txt(request): lines = [ "Contact: mailto:firstname.lastname@example.org", ] return HttpResponse("\n".join(lines), content_type="text/plain")
We’re using Django’s
require_GET decorator here to restrict to only GET requests. Class-based views already do this, but we need to think about it ourselves for function-based views.
We generate the
security.txt content inside Python, by combining a list of lines using
Second, we’d need to add a urlconf entry:
from django.urls import path from core.views import security_txt urlpatterns = [ # ... path(".well-known/security.txt", security_txt), ]
We’d want to check this works by running
manage.py runserver and visiting the URL, for example
This approach generates the file from a Python list. Doing this allows us some flexibility, for example we could dynamically generate the contact line from a list of staff users’ emails. If you don’t need this flexibility, you could also store the contents of
security.txt in a file. I wrote about this approach previously, when covering the similar
robots.txt URL in How to add a robots.txt to your Django site.
As we saw above, one of the advantages of serving
.well-known URL’s from Django is that we can add automated tests. These tests will guard against accidental breakage of the code, or removal of the URL.
We can add some basic tests for our
security.txt URL in a file like
from http import HTTPStatus from django.test import TestCase class SecurityTxtTests(TestCase): def test_get(self): response = self.client.get("/.well-known/security.txt") self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response["content-type"], "text/plain") content = response.content.decode() self.assertTrue(content.startswith("Contact: mailto:email@example.com")) def test_post_disallowed(self): response = self.client.post("/.well-known/security.txt") self.assertEqual(HTTPStatus.METHOD_NOT_ALLOWED, response.status_code)
We can run these tests with
python manage.py test core.tests.test_views. It’s also a good idea when adding new tests to check that they are being run, by making them fail. We could do this here by commenting out the entry in the URL conf.
You can adapt this approach to serve any
.well-known URL. If you need to serve JSON, such as for
JsonResponse class provides a handy shortcut to do this.
Improve your Django develompent experience with my new book.
One summary email a week, no spam, I pinky promise.
- How to add a robots.txt to your Django site
- How to Unit Test a Django Form
- Getting a Django Application to 100% Test Coverage