# Do we need an easier toggle for \`request.is\_secure()\` to be True?

**URL:** <https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751>\
**Category:** Django Internals\
**Created:** [May 6, 2023, 9:05am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751 "2023-05-06T09:05:32Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [May 6, 2023, 9:05am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/1 "2023-05-06T09:05:32Z")

</div>

Hi all.

[`HttpRequest.is_secure()`](https://docs.djangoproject.com/en/4.2/ref/request-response/#django.http.HttpRequest.is_secure): Returns `True` if the request is secure; that is, if it was made with HTTPS.

Django isn’t magic here: it goes on a couple of clues as to what to answer there:

- [SECURE\_PROXY\_SSL\_HEADER](https://docs.djangoproject.com/en/4.2/ref/settings/#secure-proxy-ssl-header) setting, if set.
- For WSGI, the value of the `"wsgi.url_scheme" ` environ key, otherwise.

Getting this right has always been a bit of a pain.

Since Django 4.0 though, CSRF protection uses this to check the Origin header, and there are **lots** of posts of people tripping up on it.

- Just here in Mystery Errors last week: [Getting Forbidden (CSRF cookie not set.): while trying to login to Django Admin Page](https://forum.djangoproject.com/t/getting-forbidden-csrf-cookie-not-set-while-trying-to-login-to-django-admin-page/20645/1)
- Separately, I posted a TIL about it, having hit the same (again) deploying a fresh project: [CSRF and Trusted Origins in Django 4.x+](https://noumenal.es/notes/til/django/csrf-trusted-origins/) — This suggests `SECURE_PROXY_SSL_HEADER` but mentions a WSGI middleware, and I added [a gist for a Django middleware equivalent](https://gist.github.com/carltongibson/648099cd34b2c0a18e948c917a5c48fd)

But there are legion. A search for [CSRF trusted origins](https://www.google.com/search?client=safari&rls=en&q=CSRF+trusted+origins&ie=UTF-8&oe=UTF-8) has the same issue again and again and again.

I think 🤔 the main issue is that `SECURE_PROXY_SSL_HEADER` is hard to get right, and varies between environments significantly.

**But** , in contrast to the old days, HTTPS is the norm now. Having a really fiddly setting, dependent on equally fiddly settings in my hosting environment, just to get the default to work seems (maybe) an undue burden now.

Should we add some toggle — Grrr, a setting? 😬 — to more easily say, _This project will always be served with HTTPS_, and have `HttpRequest.is_secure()` always defer to that?

This is security sensitive, so we must be careful not just to jump on Yes. 🚨

---

<div class="post-metadata">

**Author:** ![apollo13](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/apollo13/32/30_2.png) [@apollo13](https://forum.djangoproject.com/u/apollo13)\
**Post date:** [May 6, 2023, 9:38am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/2 "2023-05-06T09:38:18Z")

</div>

While I understand that people are having an issue with this, it is worth noting that the combination of `SECURE_PROXY_SSL_HEADER` and `wsgi.url_scheme` _always_ fixes this. I do give you that it requires an understanding of how http headers are used to communicate between proxies and the wsgi server, but that is not the worst thing for something security sensitive.

Your TIL is interesting for multiple reasons: First off, it obviously fixes the problem for you by tying directly into the WSGI env variables. It also has the upside that for instance Gunicorn can use this information for logging (I am doing similar things with the remote ip, which Gunicorn would otherwise only see as the proxy in front of it). So while your approach does have the upside of making this work with everything being able to work of WSGI env variables (be that gunicorn or for instance a sentry and other tools that tap into that info) it also has the downside of being code and hard to adjust to different environments if it extends to more than just HTTPS handling (though I agree that is not your goal here).

So all in all I am somewhere between `+0` and `-0` personally – I can see this being a pain for people, but I think that the current code works well enough for this. On the other hand (and I know how to configure this stuff by heart) I am also often in the situation where I _know_ that I have a proxy in the same network namespace and the only way to talk to my app is secured via HTTPS, and I really don’t want to waste time remembering which header my proxy uses this time.

This whole thing also reminds me a bit of [https://groups.google.com/g/django-developers/c/YzRj7OXpLkk](https://groups.google.com/g/django-developers/c/YzRj7OXpLkk) (thanks to @sarahboyce for putting that up – took me a while to find it since I was searching for it in the forum :D). While a `BASE_URL` wouldn’t tell you whether you are actually on HTTP or HTTPS (it solely is ment to provide a “default”) it also taps into two things that are kinda “hard” to get. One is the domain you are currently on (depending on the proxy this might or might not be the normal `Host` header but an `X-Forwarded-Host` header) and the other is the subpath that Django is mounted on.

Hope that helps 🙂

---

<div class="post-metadata">

**Author:** ![vanschelven](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/vanschelven/32/20486_2.png) [@vanschelven](https://forum.djangoproject.com/u/vanschelven)\
**Post date:** [October 1, 2024, 9:43am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/3 "2024-10-01T09:43:46Z")

</div>

I’m 100% on board with Carlton’s line of thought here. +1 on just directly exposing this in a setting.

I think the other big thing from that TIL is that Django’s default CSRF Middleware doesn’t expose enough information to aid in debugging. Which is why recommendations for “just set CSRF\_TRUSTED\_ORIGINS” are plastered across the internet.

My own solution for that one is [GitHub - bugsink/verbose\_csrf\_middleware: Django CSRF middleware, but more verbose in its failures.](https://github.com/bugsink/verbose_csrf_middleware)  
Arguably, such a solution should (could) be part of Django, but given the security tradeoffs I haven’t bothered to push for that yet.

---

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [October 1, 2024, 9:46am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/4 "2024-10-01T09:46:45Z")

</div>

@CodenameTim posted a similar point about the middleware over here:

> [@CSRF invalidation improvements](https://forum.djangoproject.com/t/csrf-invalidation-improvements/25572):
>
> The exception flow for CSRF issues is a bit difficult. I think we can make it easier for developers to determine the root cause of a CSRF invalidation by doing the following: Log a detailed explanation of the error. For example when a CSRF token is missing from the headers, the error message presented to the user is “CSRF token missing.” This doesn’t explain where it’s missing or how it could potentially be added in. For new Django devs this is a very cryptic message. (less sure) Use a unique …

You might want to also join the discussion there @vanschelven. (I think there’s a general vibe in support of something there, so it’s bandwidth to get it over the line…)

---

<div class="post-metadata">

**Author:** ![quan](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/quan/32/10905_2.png) [@quan](https://forum.djangoproject.com/u/quan)\
**Post date:** [October 7, 2024, 10:29am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/5 "2024-10-07T10:29:01Z")

</div>

There is one use case that `SECURE_PROXY_SSL_HEADER` and `wsgi.url_scheme` is not enough to determine if user is visiting HTTPS. That is when Django is behind two layers of reverse proxies:

```auto
       https http http
User ----------> CloudFlare -------> Nginx -------> Django

```

In this case, even though CloudFlare sets correct `X-Forwarded-Proto: https`, Nginx will overwrite it to `http` and make Django think that user is visiting plain HTTP.

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [October 7, 2024, 11:57am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/6 "2024-10-07T11:57:59Z")

</div>

> [@quan](#):
>
> Nginx will overwrite it to `http` and make Django think that user is visiting plan HTTP.

I would think in this situation that you could use the [`proxy_pass_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_header) configuration parameter to pass the `X-Forwarded-Proto` from what is received in the request being proxied by CloudFlare.

---

<div class="post-metadata">

**Author:** ![quan](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/quan/32/10905_2.png) [@quan](https://forum.djangoproject.com/u/quan)\
**Post date:** [October 8, 2024, 7:53am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/7 "2024-10-08T07:53:40Z")

</div>

Yes, I’m using this method, but I feel that it is just a workaround.

---

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [October 8, 2024, 8:12am UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/8 "2024-10-08T08:12:27Z")

</div>

Hi @quan,

My take here is that _Configure your web server properly_ is the (strictly) **correct** answer.

But I think it’s too much of a lift — it’s too esoteric/opaque — to be the default we go with.

I think I would point folks generally to either of the WSGI or Django Middleware approaches, currently, but I (still) think those are a little too advanced/hard given that ≈everybody ≈always uses HTTPS these days.

I’m not generally a big fan of settings, but I think a single toggle here, used by `HttpRequest.is_secure()` as the first path, would be a good usability addition.

(I vividly recall the first time I had to work this out myself. It must have taken two days to get right.)

**Edit** : Actually, glancing again at [the Django Middleware example](https://gist.github.com/carltongibson/648099cd34b2c0a18e948c917a5c48fd) (linked above), if Django shipped that (or better) and the **fix** here was just to add a single line to `MIDDLEWARE`, I think that would be fine too.

**Another Edit** : @apollo13 points out that a middleware might just as well adjust the WSGI environment (or ASGI scope, and request), which would (of course) work too, and would have benefits for logging and such. If we go this route we can investigate.

---

<div class="post-metadata">

**Author:** ![quan](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/quan/32/10905_2.png) [@quan](https://forum.djangoproject.com/u/quan)\
**Post date:** [October 8, 2024, 2:00pm UTC](https://forum.djangoproject.com/t/do-we-need-an-easier-toggle-for-request-is-secure-to-be-true/20751/9 "2024-10-08T14:00:24Z")

</div>

Thanks @carltongibson and @KenWhitesell

I didn’t read Ken carefully. At a glance, I misread that he proposed to use `proxy_set_header` and thought that it was workaround (that was what I did also). But now I read again, `proxy_pass_header` is a correct solution.
