[Django] #33529: Possibly Incorrect statement in the docs about CRSF tokens

4 views
Skip to first unread message

Django

unread,
Feb 19, 2022, 10:45:48 AM2/19/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
----------------------------------------+------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: new
Component: contrib.auth | Version: 4.0
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
----------------------------------------+------------------------
https://docs.djangoproject.com/en/4.0/ref/csrf/ says:

> Note
>
> The CSRF token is also present in the DOM, but only if explicitly
included using csrf_token in a template. The cookie contains the canonical
token; the CsrfViewMiddleware will prefer the cookie to the token in the
DOM. Regardless, you’re guaranteed to have the cookie if the token is
present in the DOM, so you should use the cookie!

How from my testing, I think it prefers the DOM token to the cookie. You
can prove this behaviour as follows:

1. Log out
2. Open two tabs to the login screen; tab A and tab B
3. Log in on tab A, this will destroy the Anonymous session, and create a
new session.
4. Then switch to tab B and Log in
5. You will get a CSRF Error because it is using the DOM token linked to
the anonymous sessions (that was destroyed), not the cookie.

I think instead of correcting the docs, the cookie first behaviour is
desired. However with the cookie first approach, why does one need the
CSRF DOM token (`{% csrftoken %}`)?

I can only think of the unusual case when one is performing AJAX requests
with cookies inaccessible via javascript.

--
Ticket URL: <https://code.djangoproject.com/ticket/33529>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Feb 19, 2022, 2:21:00 PM2/19/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------

Reporter: Michael | Owner: nobody
Type: Bug | Status: new
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* cc: Florian Apolloner, Shai Berger (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:1>

Django

unread,
Feb 19, 2022, 2:52:53 PM2/19/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------

Reporter: Michael | Owner: nobody
Type: Bug | Status: new
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Florian Apolloner):

I honestly have no idea what the note is trying to say. The
`CsrfViewMiddleware` has no access to the DOM after all :D What do I miss?

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:2>

Django

unread,
Feb 19, 2022, 3:02:19 PM2/19/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* status: new => closed
* resolution: => invalid


Comment:

Also, this note was changed in 5d80843ebc5376d00f98bf2a6aadbada4c29365c,
so the problem seems to be outdated now (see a short
[https://github.com/django/django/pull/14776#discussion_r725511580
discussion]) because it doesn't say anything about `CsrfViewMiddleware`
preferences. Am I right?

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:3>

Django

unread,
Feb 19, 2022, 3:07:37 PM2/19/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Florian Apolloner):

Yeah this seems about right.

> I think instead of correcting the docs, the cookie first behaviour is
desired.

The admin doesn't use Javascript so it has to use the token from the DOM
to send along in the form submission which then is compared to the cookie
server side.

> However with the cookie first approach, why does one need the CSRF DOM
token ({% csrftoken %})?

The CSRF token needs to be part of the forms that your browser sends. Only
when you are using JS you have the option of fetching the token from
somewhere else.

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:4>

Django

unread,
Feb 20, 2022, 3:53:20 PM2/20/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Michael):

Replying to [comment:2 Florian Apolloner]:


> I honestly have no idea what the note is trying to say. The
`CsrfViewMiddleware` has no access to the DOM after all :D What do I miss?

CSRF token that is POSTed, which the middleware sees, originates from a
CSRF token that is embedded in the DOM (usually via a `{% csrftoken %}`,
as noted in the orginal post).

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:5>

Django

unread,
Feb 21, 2022, 2:30:37 AM2/21/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Florian Apolloner):

Hi Michael,

the CSRF token is POSTed but that does not mean it has to originate from
the DOM. The middleware operates on the HTTP level, all it sees is post
data, it has no concept of the DOM. That is why I ment I do not understand
what the note is (well was, this is fixed in the dev branch) trying to
say.

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:6>

Django

unread,
Feb 21, 2022, 3:11:02 AM2/21/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Michael):

Replying to [comment:6 Florian Apolloner]:


> Hi Michael,
>
> the CSRF token is POSTed but that does not mean it has to originate from
the DOM. The middleware operates on the HTTP level, all it sees is post
data, it has no concept of the DOM. That is why I ment I do not understand
what the note is (well was, this is fixed in the dev branch) trying to
say.

Hi Florian,

Thanks for this and the clarification of how the admin site handles it. I
didn't reply to the other points because I was reading up a bit, because I
think I don't understand the whole system and did not want to waste your
time. I have also been reading that diff, and I see it changes the text
from

The CSRF token is also present in the DOM, but only if explicitly
included
using `csrf_token` in a template. The cookie contains the canonical
token; the ``CsrfViewMiddleware`` will prefer the cookie to the token
in
the DOM. Regardless, you're guaranteed to have the cookie if the token
is
present in the DOM, so you should use the cookie!

to

The CSRF token is also present in the DOM in a masked form, but only


if
explicitly included using `csrf_token` in a template. The cookie

contains the canonical, unmasked token. The
:class:`~django.middleware.csrf.CsrfViewMiddleware` will accept
either.
However, in order to protect against `BREACH`_ attacks, it's
recommended to
use a masked token.

I am guessing the new behaviour is if the CSRF Middleware receives a
request that has the `X-CSRFToken` header, and a `csrftoken` key in the
POST data, it will prefer the POST data?

Maybe a note about how the CSRF Middleware verifies the token when :
`X-CSRFToken` header *and* the POST `csrftoken` entry are *both*
provided. This is a common scenario if one logs out and in another tab,
and uses JavaScript Ajax calls that can add both:

E.g.:
1. There is a POST `csrftoken` entry, the middleware tries it, and raises
CSRF exception, even those there is a valid `X-CSRFToken` header

Or:
1. There is a POST `csrftoken` entry, the middleware tries it, fails...
2. Check whether there is a `X-CSRFToken` header, it exists and passes,
request is okay

Or.
1. Tries `X-CSRFToken` header first it exists and passes, request is okay

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:7>

Django

unread,
Feb 21, 2022, 3:29:30 AM2/21/22
to django-...@googlegroups.com
#33529: Possibly Incorrect statement in the docs about CRSF tokens
------------------------------+--------------------------------------
Reporter: Michael | Owner: nobody
Type: Bug | Status: closed
Component: contrib.auth | Version: 4.0
Severity: Normal | Resolution: invalid

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------

Comment (by Florian Apolloner):

> I am guessing the new behaviour is if the CSRF Middleware receives a


request that has the X-CSRFToken header, and a csrftoken key in the POST
data, it will prefer the POST data?

Yes it will prefer POST data but I do not think this is new.

> Maybe a note about how the CSRF Middleware verifies the token when :
X-CSRFToken header *and* the POST csrftoken entry are *both* provided.
This is a common scenario if one logs out and in another tab, and uses
JavaScript Ajax calls that can add both

This should not happen imo. Normal forms always use the token in the form.
Ajax should use one or the other but not both. `X-CSRFToken` is mostly
there if you are posting JSON and not submitting a normal form via Ajax.

Btw with at least the Django dev version, we verify the origin as well in
which case the tokens are not used at all :)

--
Ticket URL: <https://code.djangoproject.com/ticket/33529#comment:8>

Reply all
Reply to author
Forward
0 new messages