Django 3.1 gotcha: Referrer Policy has a new default, and it might break iframes and links

TL;DR:

If you have a Django application, and you've upgraded to 3.1 or later, and you make extensive use of iframes or the referrer attribute in some way, you will want to think about setting SECURE_REFERRER_POLICY so that you aren't unwittingly walking into the new default of same-origin.

Backstory

I'm sharing a hard lesson that we learned on a project at work, having to do with upgrading to Django 3.1.

This project involves at it's core experience an iframe. Inside that iframe is another application that depends on the parent frame's referrer attribute.

What is a "referrer"?

A "referrer" is an attribute that a parent page shares with either an <iframe> or a link that let's that other frame/page know "this URL referred the user to you".

If Site A has a link to Site B, and a user clicks on that link, when they arrive at Site B, Site B would know what URL sent the user to them.

The same thing applies to <iframes> - if Site A has an iframe to Site B, Site B would then know what was referring to it.

What is "referrer-policy", then?

You may or may not want to share that information. By setting a referrer policy, you are declaring in what circumstances you want to share the referrer attribute. There are lots of different options that you can specify.

As of Django 3.0, the framework allows you to set this policy.

What changed in Django 3.0?

Django 3.0 added a new setting - SECURE_REFERRER_POLICY. It defaulted to None initially.

With that set to None, or in it's absence (which was the case in Django 2.x and below), no policy is set.

Browsers then, when they get a response in this situation, default to the policy no-referrer-when-downgrade - more on what that means in a minute.

What changed in Django 3.1, then?

In Django 3.1 SECURE_REFERRER_POLICY got a new default - same-origin. See the note here.

Because we had never explicitly set SECURE_REFERRER_POLICY, that new policy went into effect in our parent application.

Why is "same-origin" problematic?

Well, same-origin isn't problematic. In fact, it does make a reasonable default. It's more secure.

When the parent has the policy of same-origin, it will only share the referrer attribute with other sites that have, well, the same origin. The origin is the full host URL, including subdomain. Subdomains will not share referrers when the policy is "same-origin".

In our case, we had our parent application at www.app.tld, and the second app at second.app.tld. The policy of same-origin means that the referrer would not get shared in this case, which lead to our broken second app.

How did you fix it?

The fix was pretty easy, once we got to the bottom of the issue. We added this one line to our application's settings.py:

SECURE_REFERRER_POLICY = "no-referrer-when-downgrade"

Which explicitly tells the application to use no-referrer-when-downgrade as the policy. This allowed us to share referrer between subdomains, and thus restored our second application's functionality.

no referrer when downgrade? huh?

This policy basically says "don't share the referrer when the child/link has a lesser security protocol, but otherwise always share the referrer". This is the default for the web.

That means if a parent site is https://site.a and it links to http://site.b - a referrer will not be passed. In all other situations (and in our case, most importantly, across subdomains), the referrer will be passed.

Conclusion

If your app relies on iframes, or links that rely on a referrer - make sure that you have thought through what your referrer-policy is.

I suggest you review the MDN article on what the possible values are and choose what makes sense for your project.