privacycg / CHIPS

A proposal for a cookie attribute to partition cross-site cookies by top-level site

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Safely migrating existing web app cookies to Partitioned

GREsau opened this issue · comments

TL;DR: Is there a recommended process for migrating a web app to issue cookies as Partitioned which does not break ongoing cookies/sessions?

We currently have a big, old, complex, monolithic web app which uses cookies mostly for authentication. Our app is sometimes used in a cross-origin iframe e.g. embedded in a CRM, and sometimes used in a top-level context (and sometimes both, in the same browser session).

To avoid being broken by third-party cookie blocking, we are intending to add the Partitioned attribute to all cookies issued by our app. Being unable to share cookies between the iframe and top-level context is a bit of a problem, but we're mostly working-around it with some (unfortunately slightly hacky) popups and window.opener shenanigans.

Our biggest problem is finding a way to safely roll-out the change to make cookies Partitioned in a backward-compatible manner (and forward-compatible, in case we need to temporarily roll-back the change), which we need to do before browsers actually start blocking third-party cookies. Some of our enterprise customers have quite slow-moving browser update policies, which means our solution should ideally still work for browsers that don't understand/respect the Partitioned attribute.

Naively making all new Set-Cookie headers have the Partitioned attribute would cause problems for users who have ongoing sessions at the point we apply the change, e.g.

  1. A user has an unpartitioned cookie auth=some.session.data
  2. We update our app to include Partitioned in all Set-Cookie headers
  3. The user tries to log out, causing the server to send a response header like Set-Cookie: auth=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Partitioned
  4. Because the existing cookie in the browser is unpartitioned, the browser does not delete the cookie, so the user remains logged in despite their best efforts

We can work-around this specific case by, when issuing Set-Cookie header with an Expires value in the past (i.e. when deleting a cookie), duplicate the header - once with Partitioned, and once without. This ensures that we delete the cookie no matter whether it was issued as Partitioned or not - although it does violate the recommendations of RFC 6265:

Servers SHOULD NOT include more than one Set-Cookie header field in the same response with the same cookie-name.

However, we still have a problem when trying to update an existing cookie without deleting it, e.g.

  1. A user has an unpartitioned cookie auth=some.session.data
  2. We update our app to include Partitioned in all Set-Cookie headers
  3. The user's session data changes (e.g. when it is renewed) causing the server to send a response header like Set-Cookie: auth=renewed.session.data; Partitioned
  4. Because the existing cookie in the browser is unpartitioned, the browser does not replace the existing cookie - it instead adds a new cookie with the same name
  5. Subsequent requests from the browser include both cookie values in the request header, e.g. Cookie: auth=some.session.data; auth=renewed.session.data

In this case, we would generally want the server to always use the most recently-set cookie value. But with the current browser behaviour, we would need to rely on this always being the last value for the cookie name - this is also against RFC 6265:

Although cookies are serialized linearly in the Cookie header, servers SHOULD NOT rely upon the serialization order. In particular, if the Cookie header contains two cookies with the same name (e.g., that were set with different Path or Domain attributes), servers SHOULD NOT rely upon the order in which these cookies appear in the header.

Thanks for the question, @GREsau!

First, as it pertains to finding a more elegant solution for your use-case, I would recommend looking into the FedCM API, since that is the preferred solution to keep login working post-third-party cookie phaseout.

Secondly, note that for Chrome, we are planning to deploy some short-term heuristics to keep login use cases such as yours working; so if your user flow triggers the heuristics, you may find that your iframe continues to retain access to unpartitioned third-party cookies.

Now, getting to your question - given the complications that you pointed out with using identical cookie names with different attributes, I think it may be best to maintain two duplicate/synced cookies with different names for the medium-term.

This means, you should keep your current auth cookie without the Partitioned attribute; but temporarily introduce a new cookie called auth-partitioned that is synced to contain the same value as the auth cookie, but additionally has the Partitioned attribute specified.

  • This means that every time you set or update your authentication cookie; you should attempt to set both the auth and the auth-partitioned cookie.
    • On clients that block third-party cookies, but don’t support CHIPS yet; both cookies are dropped since these clients will ignore the Partitioned attribute and treat both as unpartitioned third-party cookies.
    • On clients that block third-party cookies, but support CHIPS; auth is dropped, but auth-partitioned is accepted.
    • On clients that don’t block third-party cookies, regardless of whether they support CHIPS; both auth and auth-partitioned are accepted.
  • When your application needs to read the authentication cookie, you should look for auth-partitioned first; but if you have to temporarily roll back the change, you can fall back to looking for the auth cookie.
  • When the user logs out, you would expire both the auth and auth-partitioned cookies.
  • After a suitable amount of time, once you feel the majority of users have had their cookies refreshed, you could add Partitioned to the original auth cookie and retire auth-partitioned.

Do you see any issues with this scheme?

Hi @krgovind, thanks for the info - and sorry for the slow response, I was off of work for most of december

We looked into FedCM but it wasn't really viable for us in the short/medium-term for a few reasons. Off the top of my head, these include:

  • Lack of cross-browser support
  • FedCM still seems to be a draft/experimental browser feature, making me hesitant to rely on it for our product's core functionality (e.g. what if the spec changes?)
  • It would cause a significant change in user experience, which would require our customers to retrain their users, who may not be tech-savvy
  • This would essentially be a brand-new authentication mechanism - implementing, testing and maintaining this would be very expensive
  • Our app is not a proper Single-Page Application, so I believe it would still require some sort of cookie for authentication

I don't think the exemption heuristics would work for us without tweaking our auth flow, since the first cookie is set before any user interaction (before the login page is even displayed). Even if it did work, this would of course just be kicking the can down the road - assuming these heuristics are just temporary, we would still need to find a longer-term fix.

Introducing new partitioned cookies with an extra -partitioned (or other arbitrary string) added to the name would probably work, but there are still significant challenges with that solution. Mainly, we would need to update all usages of all cookies within our app - for the trivial example where there's a single "auth" cookie, this would be fine, but of course in practice it's not so simple. As I said above, we're unfortunately dealing with "a big, old, complex, monolithic web app". This has many usages of cookies in both first-party code and third-party libraries, some of which are read by JavaScript on the front-end or layer-7 load balancers - so all of these would need updating. The cookies we're setting are also fairly large, so we've had to take steps in the past to compress them to avoid hitting browser/proxy cookie/header limits - duplicating each cookie would cause us to hit those limits again (we actually tried a solution similar to this earlier, and ran into this exact problem in our test environment!).

For now, the solution we're going ahead with is basically to:

  • When clearing a cookie, clear it both with and without Partitioned attribute
  • When setting a cookie with the Partitioned attribute, we first clear it without the Partitioned attribute

Our full solution is a little more complicated with some extra steps in order to facilitate a backward-/forward-compatible "expand-contract" rollout, but that's the gist of it.

This means that we don't need to update any code that reads cookies, since they'll have the same name whether or not they're partitioned. It does mean we need to run some extra logic when setting cookies, but fortunately we're able to do this for all cookies with a custom module on our webserver, so we don't need to hunt down each individual place in code where we set a cookie.

This does of course mean we will be violating the "SHOULD NOT"s of RFC 6265 which isn't great. But practically, I think the risk of breaking non-compliant browsers is less than the cost of having to find alternative solutions that we can implement in a timely manner.

@GREsau Thanks for the feedback regarding FedCM. I'll pass this on to others on my team who are thinking about login use-cases.

Glad to hear that you've figured out a suboptimal scheme, but one that works for you.

Is there a recommended process for migrating a web app to issue cookies as Partitioned which does not break ongoing cookies/sessions?

Very belated reply: Transition from unpartitioned to partitioned cookies provides a basic overview of this process.