О чем речь?

Похоже, что в череде всевозможных инициатив по борьбе со «сторонними» куками начал проглядываться конец. Давайте разберемся, что происходит и зачем все это нужно.

Куки — браузерный механизм, который позволяет хранить данные по пользователю с привязкой к домену и затем передавать эти данные при запросах. Куку можно установить как со стороны сервера через специальный заголовок Set‑Cookie, так и в браузере прямо на странице через javascript. После этого кука будет передаваться при каждом запросе из браузера к домену.

Куки позволяют воплощать в жизнь множество как полезных, так и сомнительных механик. Из неприятных — возможность отслеживания пользователя сторонними сайтами или увеличение риска CSRF.

Как это работает сейчас?

First‑party куки — это куки того сайта, который открыт в вашем браузере (его домен вы видите в адресной строке). Third‑party — это куки запросов, которые ходят за данными на другие домены (загрузка картинок и скриптов с CDN, запросы за рекламой). Легко догадаться, что если какой‑то сервис установил свои скрипты на большинстве сайтов, то отслеживание пользователей для него остается лишь делом техники.

Помимо разделения на first и third‑party для нас также важно разделение на 3 режима работы кук с точки зрения разграничения по сайтам (чуть позже для нас это станет важно). Эти режимы работы регулируется параметром SameSite:

  • SameSite=None означает, что кука не привязана к сайту. После того как она выставлена, она будет передаваться как в first‑party, так и в third‑party режиме.

  • SameSite=Strict означает, что кука передается только в first‑party режиме (пока находишься на сайте)

  • SameSite=Lax работает примерно как Strict за тем исключением, что кука отправляется еще и при навигации в браузере (при запросе за исходным HTML)

В большинстве браузеров уже есть те или иные механизмы блокировки third‑party кук. В одних браузерах это ограничивается тем, что third‑party куки разрешено отправлять только на посещенные пользователем сайты. В других браузерах внедряют различные «enhanced» и «intellegent» tracking protection, либо совсем блокируют third‑paty куки. Для тех, кто захочет разобраться детально в перипетиях отдельных браузеров, есть прекрасные статьи.

В 2019 году волей сообщества дефолтное поведение кук в браузерах изменилось с SameSite=None на SameSite=Lax. Что, впрочем, привело лишь к тому, что большинство сервисов перевело свои трекеры в режим SameSite=None.

Консенсус в сообществе свелся к тому, что:
— Нужно запретить отслеживать пользователя между сайтами
— First‑party — это вполне ОК. Сайт внутри себя может позволить себе любые манипуляции с пользовательскими данными
— Third‑party — в целом тоже ОК, но только если ограничить third‑party куки одним сайтом (следите за руками ниже)

Так какие же есть решения для того, чтобы отказаться от third‑party кук и ничего не сломать?

Какие есть решения?

На данный момент механизма разграничения third‑party кук не существует, поэтому для решения проблемы была изобретена еще одна «фича» — Cookies Having Independent Partitioned State (CHIPS). «Чипсы» (CHIPS) приходят на замену «печенек» (Cookies), кек. В рамках этой фичи у кук появляется еще один атрибут — Partitioned. Кука с таким атрибутом будет отправляться только с того сайта, на котором она была выставлена.

Например, пользователь заходит на сайт https://first-site.com. Сайт загружает картинку с https://avatars.com. В ответ https://avatars.com отправляет заголовок Set-Cookie с атрибутом Partitioned. Эта кука будет отправляться из браузера при каждом запросе к https://avatars.com с сайта https://first-site.com. Однако когда пользователь зайдет на другой сайт https://second-site.com и попытается загрузить ту же картинку с сайта https://avatars.com, имеющаяся кука уже не будет отправлена. Вместо этого сервер может выставить для пользователя новую куку с атрибутом Partitioned, которая будет отправляться только с сайта https://second-site.com.

При этом внедрение Partitioned кук подразумевает постепенный отказ от всех остальных third‑party кук. Все куки, работающие в режиме SameSite=None без выставленного атрибута Partitioned просто перестанут отправляться из third‑party окружения. Именно перестанут отправляться, а не просто будут сконвертированы в Partitioned. Гугл планирует раскатить эту фичу в 2 этапа — на 1% пользователей хрома с января 2024 и на 100% пользователей осенью 2024.

Это сломает множество сценариев использования сайтов. Очень часто сайтам нужно шарить куку между доменами (например между google.com и google.ru). Для того чтобы дать сайтам такую возможность, была изобретена еще одна «фича» — Storage Access API (SAA). Она позволит получить доступ к SameSite=None куке без атрибута Partitioned для запросов из third‑party окружения, но только с явного разрешения браузера или пользователя.

Как пример — я захожу на сайт first-site.com, на котором встроена авторизация через сторонний сервис authorization.com. Сайт встраивает iframe стороннего сервиса. Внутри iframe вызывается скрипт, который проверяет, может ли он получить доступ к своей SameSite=None куке. Проверка происходит через вызов await navigator.permissions.query({name: 'storage-access'}). Если пользователь ни разу не был на сайте authorization.com, или окружение не соответствует другим критериям — получаем отказ. Пример того, как это должно работать, лежит тут. Если промис резолвится с объектом { state: 'prompt' }, значит для получения доступа к SameSite=None куке необходимо спросить разрешение у пользователя.

После этого можно запросить разрешение через вызов await document.requestStorageAccess(). При необходимости апрува пользователя, prompt будет выглядеть примерно так:

authorization.com wants to use info they've saved about you
authorization.com will know that you visited first‑site.com.
Learn more about embedded content

Промис резолвится — значит у нас появился доступ к SameSite=None кукам authorization.com (как через document.cookie, так и к HttpOnly кукам при запросах из айфрейма). Реджект — извините.

При каждом обновлении страницы доступ к кукам необходимо перезапрашивать через requestStorageAccess. Prompt показывается только один раз (последующие разы разрешение выдается автоматически).

Все, что написано ниже, мне удалось найти в документации, но не посчастливилось проверить своими руками. Если вам удастся, напишите в комментариях:)

Так, ну с iframe проблема, хоть и неудобно, но решается. А как быть с ресурсами типа картинок, которые не могут подгрузить для себя скрипт и запросить разрешение? Для таких случаев разработчики Chrome придумали еще одну «фичу» — requestStorageAccessFor. Это API позволяет сделать «обратный финт» — запросить доступ к кукам не с самого домена, а «для» домена. Например, я захожу на сайт first-site.com и собираюсь загрузить аватарку с домена avatars.com. Прежде, чем начать загрузку, я запрашиваю разрешение через await document.requestStorageAccessFor('https://avatars.com'). Если все успешно — при запросе за картинкой отправятся SameSite=None куки с домена avatars.com.

Chrome также внедряет еще одну полезную «фичу», про которую нужно знать. Related Website Sets (RWS) позволяет создавать группы сайтов с общими куками. Для того чтобы это заработало — нужно буквально закоммитить список своих сайтов в специальный файлик на гитхабе. В этом файле указывается primary сайт, associatedSites — которые являются неотъемлемой частью сервиса и требуют обмена пользовательскими данными, а также их ccTLDs для разных регионов. Чтобы подтвердить свои благородные намерения, необходимо добавить на свой сайт файлик /.well-known/related-website-set.json с теми же данными. Если вы сделали все правильно, вам не нужно будет запрашивать разрешение у пользователя для обмена куками.

Комментарии (16)


  1. Daniil_Palii
    11.11.2023 09:02
    +3

    Что помешает рекламодателям абъюзить RWS? Можно взять и объеденить все сайты на которых размещена твоя реклама под свой primary сайт. Для сайтов которые хотят зарабатывать с рекламы это будет обязательное условие.


    1. senaev Автор
      11.11.2023 09:02
      +3

      Процесс добавления нового сета прописан тут.

      Он включает в себя набор автоматических и ручных проверок. Просто так прийти и законтрибьютить туда любые списки любых сайтов не получится. Сами сайты тоже валидируются через механизм .well-known, который (как я понимаю) не позволит переиспользовать один сайт в двух разных сетах

      Также "концептуально" подразумевается что все сайты в RWS должны быть аффилированы между собой, и эта связь должна быть прозрачна и понятна для пользователей сайта и прописана в исходном JSON файле


  1. 0xC0CAC01A
    11.11.2023 09:02
    +1

    Вопрос в тему. Есть плагины для Файрфокса или Хрома, позволяющие в разных табах иметь отдельные, гарантированно непересекающиеся по кукиз браузеры?


    1. dartraiden
      11.11.2023 09:02
      +7

      В Firefox для этого есть контейнеры.


    1. senaev Автор
      11.11.2023 09:02

      Кажется, вы только что описали режим "инкогнито", но там разделение идет по окнам, а не по вкладкам (в том числе потому, что нужно исключить отслеживание браузерными плагинами)


    1. alexac
      11.11.2023 09:02

      В хроме для этого есть профили, но они в разных окнах, не табах.


  1. johnfound
    11.11.2023 09:02
    +4

    Обвязывать работа браузера, да и вообще работа WWW с конкретным бизнесом (github), это так прекрасно!


    1. senaev Автор
      11.11.2023 09:02

      Справедливости ради, RWS внедряют только в Chrome, который является продуктом гугла.


    1. Kurochkin
      11.11.2023 09:02

      Полагаю, это делается для "открытости"/"прозрачности" - мол, если сомневаетесь в нашей честности, то вот же ж, смотрите на Гитхабе, всего-то пару гигабайт полистать.

      Заодно и системные требования Хрома можно обосновать - "мы же обязаны выполнять требования стандарта"


  1. VADemon
    11.11.2023 09:02
    +1

    Access requests are automatically denied if the browser detects that the user hasn't interacted with the embedded content in a first-party context recently (in Firefox, "recently" means within 30 days).

    Я правильно понял, что сайт запрашивающий API сможет узнать, посещал ли пользователь тот другой сайт в течении 30 дней?


    1. senaev Автор
      11.11.2023 09:02

      Получается так. Хотя вряд ли это будет очень полезно.

      Пока доступ к кукам есть (30 дней с момента одобрения), у тебя есть безлимит на получение любой информации о пользователе, включая посещения сайта. Последнее, что что можно узнать - что 30 дней прошло и доступ аннулирован.


  1. olku
    11.11.2023 09:02
    +1

    Какой сейчас консенсус - домен и поддомен это все ещё First-Party?


    1. senaev Автор
      11.11.2023 09:02

      Тут главное не перепутать теплое с мягким

      1) First и third party - это про то, откуда запрос идет (с того же домена, где куки и так доступны через JS, или с другого)
      2) Partitioned и Unpartitioned - про то, делятся ли куки (и другие стораджи) по top-frame сайту (как раз про это писал недавно заметку)
      3) Разделение по доменам (и поддоменам) и для Partitioned и для Unpartitioned и для first-party и для third-party кук работает одинаково, через атрибут Domain (который работает очень неочевидным не очень очевидным образом), или через префикс __Host.


  1. grishkaa
    11.11.2023 09:02
    +4

    Я во всех своих браузерах выключил third-party cookies ещё несколько лет назад, и не заметил существенных проблем. Кажется, что полезных для пользователя применений для сторонних кук просто не существует. И кажется, что если их просто убрать, не предоставив никакой замены, мир станет лучше. Если очень хочется передать данные между разными сайтами, например, для авторизации на всяких порталах с кучей доменов, это можно сделать через редиректы и гет-параметры.


    1. senaev Автор
      11.11.2023 09:02

      Боюсь, где-то тут закралась ошибка. Скорее всего в том, что вы путаете отключение third-party и включение partitionned. Рекомендую мой коммент выше.

      На данный момент авторизация через гугл работает в вашем браузере даже если вы выключили third-party куки только потому что куки продолжают быть Unpartitioned и отправляться, например, в айфреймах самого гугла (с чужого сайта мы твои куки, конечно, не отправим, но если ты вставишь айфрейм first-party сайта - то оттуда без проблем).

      Вся "заварушка" как раз для того и планируется, чтобы "через редиректы и гет-параметры" нельзя было передавать данные пользователя "для авторизации на всяких порталах с кучей доменов"


  1. stvoid
    11.11.2023 09:02
    +1

    Ох, с каким же предвкушением гемороя я читаю новости о том как порежут сторонние куки. Вроде перечитал 2 раза, но как же это запарно в случае iframe всё равно, не понятно, придется уже по факту воевать.

    Вот есть у нас на работе некоторая CRM которая расширяется средствами встраивания iframe, куда CRM проталкивает всякие параметры типа временного токена юзера, чтобы его аутентифицировать и т.п. Вот только токены эти живут 5 минут, а пользователи держат вкладки открытыми днями. Естественно приходится футентифицировать юзера, повесить ему свои куки и не дергать CRM лишний раз вопросами "а хто это?".

    Ну и само собой юзеры могут юзать расширения как в режиме встраивания в iframe, так и отдельно как обычный сайт. В общем поглядим, надеюсь лечение проблем будет не сильно замороченым.