Description
Description
In ResponseHeaderBag's setCookie() function:
cookies are set based on their domain, path, and name. However there's one more category that partitions cookies, ironically named partitioned. You can set both partitioned and unpartitioned cookies at the same time. Unfortunately the ResponseHeaderBag doesn't allow this.What's the use case you may ask? We have a site that can either be opened in an iframe or at a top level window in a new tab. We set our auth cookie to partitioned inside the iframe and not partitioned in a new tab. This is done in a classroom setting where multiple users often use a single machine. If they only use iframes or only use new tabs, then there's no problem. However, once you mix the two, you can have a case where the new tab cookie leaks into the iframe. Both cookies are then sent with requests, causing the user inside of the iframe to potentially logged in as the last user who had a new tab logged in instead.
To counteract this, we attempt to log out (delete the previous cookies) when a user logs in. However, we need to delete the unpartitioned cookie, and set the partitioned one in the same response. Setting the partitioned cookie overwrites deleting the unpartitioned cookie. The code example below shows a minimal reproduction. Right now our workaround is to use a raw header()
statement, which is less than ideal. Note that we are also using Laravel, but before we can make any PR there, we first need to make one here.
Thanks for your work!
Example
<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;
$response = new Response();
// Log out the old user outside the iframe so the cookie doesn't leak inside the iframe
$response->headers->setCookie(new Cookie('auth', null, sameSite: Cookie::SAMESITE_NONE, partitioned: false));
// Log in the new user by setting their cookie.
$response->headers->setCookie(new Cookie('auth', 'new_token', time() + 7200, sameSite: Cookie::SAMESITE_NONE, partitioned: true));
echo implode('\n', array_map(fn ($cookie) => (string) $cookie, $response->headers->getCookies()));
// expected:
// auth=deleted; expires=Mon, 06 Nov 2023 21:31:24 GMT; Max-Age=0; path=/; httponly; samesite=none
// auth=new_token; expires=Tue, 05 Nov 2024 23:31:57 GMT; Max-Age=7198; path=/; httponly; samesite=none; partitioned
// actual:
// auth=new_token; expires=Tue, 05 Nov 2024 23:31:57 GMT; Max-Age=7198; path=/; httponly; samesite=none; partitioned