О чем речь?
Похоже, что в череде всевозможных инициатив по борьбе со «сторонними» куками начал проглядываться конец. Давайте разберемся, что происходит и зачем все это нужно.
Куки — браузерный механизм, который позволяет хранить данные по пользователю с привязкой к домену и затем передавать эти данные при запросах. Куку можно установить как со стороны сервера через специальный заголовок 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)
0xC0CAC01A
11.11.2023 09:02+1Вопрос в тему. Есть плагины для Файрфокса или Хрома, позволяющие в разных табах иметь отдельные, гарантированно непересекающиеся по кукиз браузеры?
senaev Автор
11.11.2023 09:02Кажется, вы только что описали режим "инкогнито", но там разделение идет по окнам, а не по вкладкам (в том числе потому, что нужно исключить отслеживание браузерными плагинами)
johnfound
11.11.2023 09:02+4Обвязывать работа браузера, да и вообще работа WWW с конкретным бизнесом (github), это так прекрасно!
senaev Автор
11.11.2023 09:02Справедливости ради, RWS внедряют только в Chrome, который является продуктом гугла.
Kurochkin
11.11.2023 09:02Полагаю, это делается для "открытости"/"прозрачности" - мол, если сомневаетесь в нашей честности, то вот же ж, смотрите на Гитхабе, всего-то пару гигабайт полистать.
Заодно и системные требования Хрома можно обосновать - "мы же обязаны выполнять требования стандарта"
VADemon
11.11.2023 09:02+1Access 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 дней?
senaev Автор
11.11.2023 09:02Получается так. Хотя вряд ли это будет очень полезно.
Пока доступ к кукам есть (30 дней с момента одобрения), у тебя есть безлимит на получение любой информации о пользователе, включая посещения сайта. Последнее, что что можно узнать - что 30 дней прошло и доступ аннулирован.
olku
11.11.2023 09:02+1Какой сейчас консенсус - домен и поддомен это все ещё First-Party?
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
.
grishkaa
11.11.2023 09:02+4Я во всех своих браузерах выключил third-party cookies ещё несколько лет назад, и не заметил существенных проблем. Кажется, что полезных для пользователя применений для сторонних кук просто не существует. И кажется, что если их просто убрать, не предоставив никакой замены, мир станет лучше. Если очень хочется передать данные между разными сайтами, например, для авторизации на всяких порталах с кучей доменов, это можно сделать через редиректы и гет-параметры.
senaev Автор
11.11.2023 09:02Боюсь, где-то тут закралась ошибка. Скорее всего в том, что вы путаете отключение third-party и включение partitionned. Рекомендую мой коммент выше.
На данный момент авторизация через гугл работает в вашем браузере даже если вы выключили third-party куки только потому что куки продолжают быть Unpartitioned и отправляться, например, в айфреймах самого гугла (с чужого сайта мы твои куки, конечно, не отправим, но если ты вставишь айфрейм first-party сайта - то оттуда без проблем).
Вся "заварушка" как раз для того и планируется, чтобы "через редиректы и гет-параметры" нельзя было передавать данные пользователя "для авторизации на всяких порталах с кучей доменов"
stvoid
11.11.2023 09:02+1Ох, с каким же предвкушением гемороя я читаю новости о том как порежут сторонние куки. Вроде перечитал 2 раза, но как же это запарно в случае iframe всё равно, не понятно, придется уже по факту воевать.
Вот есть у нас на работе некоторая CRM которая расширяется средствами встраивания iframe, куда CRM проталкивает всякие параметры типа временного токена юзера, чтобы его аутентифицировать и т.п. Вот только токены эти живут 5 минут, а пользователи держат вкладки открытыми днями. Естественно приходится футентифицировать юзера, повесить ему свои куки и не дергать CRM лишний раз вопросами "а хто это?".
Ну и само собой юзеры могут юзать расширения как в режиме встраивания в iframe, так и отдельно как обычный сайт. В общем поглядим, надеюсь лечение проблем будет не сильно замороченым.
Daniil_Palii
Что помешает рекламодателям абъюзить RWS? Можно взять и объеденить все сайты на которых размещена твоя реклама под свой primary сайт. Для сайтов которые хотят зарабатывать с рекламы это будет обязательное условие.
senaev Автор
Процесс добавления нового сета прописан тут.
Он включает в себя набор автоматических и ручных проверок. Просто так прийти и законтрибьютить туда любые списки любых сайтов не получится. Сами сайты тоже валидируются через механизм
.well-known
, который (как я понимаю) не позволит переиспользовать один сайт в двух разных сетахТакже "концептуально" подразумевается что все сайты в RWS должны быть аффилированы между собой, и эта связь должна быть прозрачна и понятна для пользователей сайта и прописана в исходном JSON файле