How to set the new COEP, COOP, and CORP security headers in Django

2021-05-01 “Where’s my coop?”

Here are three new security headers on the block:

  1. Cross-Origin-Opener-Policy (COOP) (MDN)
  2. Cross-Origin-Resource-Policy (CORP) (MDN)
  3. Cross-Origin-Embedder-Policy (COEP) (MDN)

They don’t currently score you any points on the checker, but they’re worth looking at to improve your site’s security.

Let’s briefly look at what they do, Django’s future support for them, and how you can add them today. For a longer description see Scott Helme’s article and the above MDN links.

Cross-Origin-Opener-Policy (COOP)

COOP isolates your origin in its own browsing context group. A browsing context group is a set of documents that have references to each other, and thus live in the same memory space.

COOP isolation prevents attacks like Spectre and Meltdown, which exploit flaws in CPU’s to read protected areas of the current memory space. Such attacks allow third party origins to steal your origin’s secrets, such as session cookies, because they share your browsing context group.

The initial response from browsers to Spectre and Meltdown was to disable the JavaScript API’s necessary to execute the attacks. This meant disabling the high resolution clock and the data-sharing API SharedArrayBuffer. Setting a secure COOP header allows your site to re-enable those features.

Adding COOP comes at the cost of breaking use of the Window.opener API, when used between origins. This property allows a window, or tab, to interact with the window that opened it. This interaction is the reason browsing context groups can contain several origins in the first place. Thankfully, use of window.opener cross-origin is a rare feature, and there are normally better ways to achieve the same results.

The COOP header takes one of three values:

Django 4.0, due for release in 2022, will set the COOP header in SecurityMiddleware, with a default value of 'same-origin'. This is thanks to Ticket #31840, which was reported and started by meggles711, and completed by Tim Graham.

Cross-Origin-Resource-Policy (CORP)

The CORP header defines which origins can embed your resources (URL’s). This allows you to block other sites from loading your resources into their browsing context groups, giving them potential access to your private data.

The CORP header takes one of three values:

If some of your URL’s should should be embeddable by other sites, you should set same-origin by default and cross-origin only on your public URL’s. For a more detailed explanation, see

Django will add support for CORP in Ticket #31923, which was again reported by meggles711 and may be worked on for Django 4.0.

Cross-Origin-Embedder-Policy (COEP)

COEP is the other side of CORP. It allows you require the cross-origin resources you load to explicitly allow you to do so.

It takes one of two values:

This header is harder to add since it requires all cross-origin resources you use to set CORP. Some common third party providers already set CORP, for example Google Fonts, but others are still catching up.

Django will add support for COEP in Ticket #31923, hopefully in Django 4.0.

Reporting Modes

Both COOP and COEP have reporting support. This allows you to add a report-to clause, specifying a URL that the browser can send reports to on resources that failed to meet your policies.

COOP and COEP also support report-only modes, activated by adding Report-Only on the end of the header name, e.g. Cross-Origin-Opener-Policy-Report-Only. In this mode, the browser does not enforce the policy, but only sends reports on the failing resources. This is great for rolling out the header on larger sites.

For reporting support, you’ll need a service to receive and parse the reports, defined in the Report-To header. Check out for such a service - I previously documented setting it up in this post.

Django Middleware

Built-in support for any of these headers won’t be coming until Django 4.0, but we can add them today with a custom middleware. For example, this middleware sets all headers to their most secure values, with reporting enabled for COEP and COOP:

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

    def __call__(self, request):
        response = self.get_response(request)
        response["Cross-Origin-Embedder-Policy"] = 'require-corp; report-to="default"'
        response["Cross-Origin-Opener-Policy"] = 'same-origin; report-to="default"'
        response["Cross-Origin-Resource-Policy"] = "same-origin"
        return response

We can add this middleware in MIDDLEWARE next to Django’s SecurityMiddleware, to set the headers at the same point in response processing. We’d also want to set Report-To, with a group named “default”, as I covered previously.

This example uses the most secure values for all the headers. When deploying on a site using third party resources, we’d want to check whether they are setting the CORP header correctly, and use the report-only modes first to check pages continue to work.


We can test the above middleware like so:

from django.http import HttpResponse
from django.test import RequestFactory, SimpleTestCase

from db_buddy.core.middleware import SecurityHeadersMiddleware

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

        middleware = SecurityHeadersMiddleware(dummy_view)
        request = RequestFactory().get("/")

        response = middleware(request)

        assert (
            == 'require-corp; report-to="default"'
        assert response["Cross-Origin-Resource-Policy"] == "same-origin"
        assert (
            response["Cross-Origin-Opener-Policy"] == 'same-origin; report-to="default"'

We use a RequestFactory to generate a request and check the response has the headers correctly set.


May your origin be secure against faults in others,


Want better tests? Check out my book Speed Up Your Django Tests which teaches you to 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, python