Opened 11 months ago

Last modified 4 weeks ago

#34613 new New feature

add support for Partitioned cookies

Reported by: Oleg Korsak Owned by: nobody
Component: HTTP handling Version: 4.1
Severity: Normal Keywords: chips, cookies, csrf, partitioned
Cc: Michael Wheeler, Markus Holtermann, Alex Gaynor, Colin Murtaugh Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hi.

I'm having issues with Django app in Chrome. It is working as a standalone and embedded into IFRAME in another system. Users tend to open both ways in tabs. At some point they manage to overwrite (like re-login) cookies with session id and csrf token in one tab, but Chrome overwrites them for another one as well, while opened IFRAME has an old CSRF token in HTML. So next request fails. No issues in Firefox.

I've found following explanation:

https://developer.chrome.com/docs/privacy-sandbox/chips/

https://blog.mozilla.org/security/2021/02/23/total-cookie-protection/

So Firefox separates such cookies by default. While Chrome needs server to set a "Partitioned" flag for cookies. But... Django is unable to do so due to using standard Python Morsel cookie class, which doesn't support it.

Change History (12)

comment:1 by Michael Wheeler, 11 months ago

Cc: Michael Wheeler added

comment:2 by Markus Holtermann, 11 months ago

Hi! Partitioned cookies still seem to be quite experimental in Chrome/Safari. So, we're unlikely to add support for them right away.

But, in order to better understand the issue, can you tell us more information about the setup you have? For example, what's the domain of the site that includes the iframe. What's the domain of the site within the iframe. What cookie-related settings do you have configured?

Can you reproduce the issue in a small project?

comment:3 by Mariusz Felisiak, 11 months ago

Resolution: needsinfo
Status: newclosed

comment:4 by Raphael Michel, 4 months ago

Resolution: needsinfo
Status: closednew

Yes, this is still experimental, but Chrome is rolling out their trial to remove third-party cookies starting this month:
https://developers.google.com/privacy-sandbox/blog/cookie-countdown-2023oct

Partitioned cookies will be basically the only way left to implement interactive iframes with state. Our specific use case is this:
https://docs.pretix.eu/en/latest/user/events/widget.html

We'll need to be able to set our session cookies as partitioned quite soon and I was surprised to find that there seems to be no way to it currently, not even a hacky one.

comment:5 by Mariusz Felisiak, 4 months ago

Cc: Markus Holtermann added

comment:6 by Mariusz Felisiak, 4 months ago

Resolution: needsinfo
Status: newclosed

Raphael, I appreciate you'd like to reopen the ticket, but I'm not exactly sure what can of changes are needed it Django, is it something that can be handled by a custom middleware? Do we need to change SimpleCookie implementation? Should we mark Django cookies as "partitioned"? Is it only an issue for session cookies? etc. I'd be grateful for your insights.

in reply to:  6 comment:7 by Raphael Michel, 4 months ago

Hi Mariusz,

Replying to Mariusz Felisiak:

Raphael, I appreciate you'd like to reopen the ticket, but I'm not exactly sure what can of changes are needed it Django, is it something that can be handled by a custom middleware? Do we need to change SimpleCookie implementation?

That is the very nasty part about this: Cookie headers are as a special case by Django directly on the WSGI/ASGI layer, different from any other header, so as far as I can tell, a custom middleware can *not* implement this. Supporting it in Django requires at least an additional keyword argument to set_cookie() plus – and that is the nasty part – a change to SimpleCookie, which is in the stdlib these days.

There is already an issue for the stdlib:
https://github.com/python/cpython/issues/112713

It also has a PR:
https://github.com/python/cpython/pull/112714

But even if that get's merged soon, I don't think it will be backported to existing Python versions, so supporting this in the near future (i.e. next Django version) it would mean vendoring parts of SimpleCookie again. I'll try to figure out what parts exactly in the next days or weeks, since even if this does not make it in Django soon, we'll need to monkeypatch it in our project somehow.

Should we mark Django cookies as "partitioned"?

I think it should be opt-in, i.e. a settings flag for session cookie and CSRF cookie (similar to Secure and Httponly), and a keyword argument for set_cookie().

Is it only an issue for session cookies? etc.

No, this will affect at least session cookie and CSRF cookie, plus possibly custom cookies.

comment:8 by Mariusz Felisiak, 4 months ago

Cc: Alex Gaynor added
Component: CSRFHTTP handling
Resolution: needsinfo
Status: closednew
Triage Stage: UnreviewedAccepted

Thanks! Tentatively accepted.

But even if that get's merged soon, I don't think it will be backported to existing Python versions, so supporting this in the near future (i.e. next Django version) it would mean vendoring parts of SimpleCookie again.

That's unfortunate, here be dragons ...

I'll try to figure out what parts exactly in the next days or weeks, since even if this does not make it in Django soon ...

Much appreciated.

comment:9 by Michael Wheeler, 4 months ago

I wonder if it would be possible to follow a similar approach to the one that was used to add support for SameSite https://github.com/django/django/commit/9a56b4b13ed92d2d5bb00d6bdb905a73bc5f2f0a.

Not sure if anyone was already planning on tackling this, but if not I'd be curious about taking it on as a first time contributor.

in reply to:  9 ; comment:10 by Terence Honles, 4 months ago

Replying to Michael Wheeler:

I wonder if it would be possible to follow a similar approach to the one that was used to add support for SameSite https://github.com/django/django/commit/9a56b4b13ed92d2d5bb00d6bdb905a73bc5f2f0a.

Not sure if anyone was already planning on tackling this, but if not I'd be curious about taking it on as a first time contributor.

Thanks for the pointer here. I was actually going to write a WSGI middleware, but following what was done for SameSite I used the following:

middleware.py:

...
from http import cookies

...
cookies.Morsel._flags.add("partitioned")
cookies.Morsel._reserved.setdefault("partitioned", "Partitioned")

class CookiePartitioningMiddleware(MiddlewareMixin):
    def process_response(
        self, request: HttpRequest, response: HttpResponseBase
    ) -> HttpResponseBase:
        for name in (
            getattr(settings, f"{prefix}_COOKIE_NAME")
            for prefix in ("CSRF", "SESSION", "LANGUAGE")
            if getattr(settings, f"{prefix}_COOKIE_SECURE")
        ):
            if cookie := response.cookies.get(name):
                cookie["Partitioned"] = True

        return response

and added the middleware to my application.

Adding and respecing a ${NAME}_COOKIE_PARTITIONED would make sense for a PR, but for our use case we want to partition all cookies. It may also make sense to make sure ${NAME}_COOKIE_SAMESITE is 'None' since that is recommended for browsers which don't support partitioning via CHIPS

in reply to:  10 comment:11 by BertrandHustle, 2 months ago

Replying to Terence Honles:

Replying to Michael Wheeler:

I wonder if it would be possible to follow a similar approach to the one that was used to add support for SameSite https://github.com/django/django/commit/9a56b4b13ed92d2d5bb00d6bdb905a73bc5f2f0a.

Not sure if anyone was already planning on tackling this, but if not I'd be curious about taking it on as a first time contributor.

Thanks for the pointer here. I was actually going to write a WSGI middleware, but following what was done for SameSite I used the following:

middleware.py:

...
from http import cookies

...
cookies.Morsel._flags.add("partitioned")
cookies.Morsel._reserved.setdefault("partitioned", "Partitioned")

class CookiePartitioningMiddleware(MiddlewareMixin):
    def process_response(
        self, request: HttpRequest, response: HttpResponseBase
    ) -> HttpResponseBase:
        for name in (
            getattr(settings, f"{prefix}_COOKIE_NAME")
            for prefix in ("CSRF", "SESSION", "LANGUAGE")
            if getattr(settings, f"{prefix}_COOKIE_SECURE")
        ):
            if cookie := response.cookies.get(name):
                cookie["Partitioned"] = True

        return response

and added the middleware to my application.

Adding and respecing a ${NAME}_COOKIE_PARTITIONED would make sense for a PR, but for our use case we want to partition all cookies. It may also make sense to make sure ${NAME}_COOKIE_SAMESITE is 'None' since that is recommended for browsers which don't support partitioning via CHIPS

FYI, this doesn't seem to work for sessionid cookies, the Partitioned attr only gets set on the csrftoken.

comment:12 by Colin Murtaugh, 4 weeks ago

Cc: Colin Murtaugh added
Note: See TracTickets for help on using tickets.
Back to Top