Why are *_COOKIE_SECURE settings defaulted to False?

I’m reading through Two Scoops of Django with a colleague and we reached the Security Best Practices section. In that section it mentions setting cookie security flags in the settings to True, like so:

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

I had never heard of this setting so I looked it up in the Django docs and docs say:

“Leaving this setting off isn’t a good idea because an attacker could capture an unencrypted session cookie with a packet sniffer and use the cookie to hijack the user’s session.”

If this is a dangerous setting to leave on False, why isn’t it defaulted to True in the settings? I’m sure there’s a good reason. Before I change a Django default I’d like to learn a little more about why it was set to False initially.

This is because Django still supports HTTP only out of the box - especially for runserver, which it’s hard to move to HTTPS. We could do better for example if SECURE_SSL_REDIRECT is True or HSTS is enabled, we could default to secure cookies. Can you open a ticket?

(I’m a fan of increasing django’s default security, as per my blog post on security headers and recent changes to defaults for security headers)

1 Like

Thank you for the quick reply and the explanation.

I wrote a ticket here https://code.djangoproject.com/ticket/31260
I think I captured this conversation properly, but if you’d like me to tweak it, let me know.

I read through the SSL and HSTS Django settings and learned a thing or two. Thanks again for the reply.

Hey :wave:

Thanks for opening the issue @adamyala - it was closed as a “wontfix”, which I kind of interpret as an encouragement to discuss the solution further in the forums :blush:

A bit of DX (developer experience): I was investigating an issue where session cookies were missing in a development environment of a project with differing development and production settings (we tend to not like that). In default production settings, we have SESSION_COOKIE_SECURE=True. But in development settings, we set SESSION_COOKIE_SECURE=False and for some reason SESSION_COOKIE_DOMAIN = ".localhost". Defining SESSION_COOKIE_DOMAIN turns out to not work for me on 127.0.0.1, but another developer may have been using a different setup. I ended up removing the SESSION_COOKIE_DOMAIN setting.

To my surprise, while fiddling with the settings, I discovered that SESSION_COOKIE_SECURE=True works just fine in a non-HTTPS development on Firefox :interrobang:

That made me wonder: Did the assumption for needing SESSION_COOKIE_SECURE=False as a default change? Maybe either how Django or browsers handle secure session cookies? Or has this worked all the time? I thought runserver + http://localhost:8000 was the reason all along why we kept SESSION_COOKIE_SECURE=False as the default?

Screenshot is from http://127.0.0.1:8000, blank browser session, with SESSION_COOKIE_SECURE=True and I can confirm that logging in on the django-admin on a somewhat minimal project works.

It seems that SESSION_COOKIE_SECURE=True works for me in Firefox, but it doesn’t work for my co-maintainer on Safari. So there’s definitely still some browser/OS-related differences whereby SESSION_COOKIE_SECURE=False is still necessary.

There definitely (used to be at least) browser differences on how this was handled.

The current standard behaviour is the following…quoted from Using HTTP cookies - HTTP | MDN, emphasis is mine.

A cookie with the Secure attribute is only sent to the server with an encrypted request over the HTTPS protocol. It’s never sent with unsecured HTTP (except on localhost), which means man-in-the-middle attackers can’t access it easily. Insecure sites (with http: in the URL) can’t set cookies with the Secure attribute.

Chrome definitely had a bug in the past where secure cookies from localhost were not set in the browser. Possibly safari has or had a similar bug.